当我们要访问我们的一些资源时,要先执行 一些过滤器,spring security就只通过下图所示的过滤器链实现的
当在项目中添加了springsecurity的依赖后,springboot就会自动的把这些filter添加进去,上图中绿色的过滤器是可以自己配置的(usernamepasswordfilter就是表单登录的过滤器),蓝色和橙色的过滤器是必须执行的
我们来看一下实现登录验证的usernamepasswordfilte是如何工作的
首先会进入usernamepasswordfilte(未认证的authentication)然后进入ProviderManager寻找合适的provider并调用provider.authenticate(authentication);再然后就是UserDetailsService接口的实现类,最后回到UsernamePasswordAuthenticationFilter,最后就得到一个认证的authentication
UsernamePasswordAuthenticationFilter.java
// 继承了AbstractAuthenticationProcessingFilter
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
// 认证请求的方式必须为POST
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}
// 获取用户名
String username = obtainUsername(request);
// 获取密码
String password = obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
// 用户名去空白
username = username.trim();
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
}
AbstractAuthenticationProcessingFilter.java
public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean
implements ApplicationEventPublisherAware, MessageSourceAware {
// 过滤器doFilter方法
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
/*
* 判断当前filter是否可以处理当前请求,若不行,则交给下一个filter去处理。
*/
if (!requiresAuthentication(request, response)) {
chain.doFilter(request, response);
return;
}
if (logger.isDebugEnabled()) {
logger.debug("Request is to process authentication");
}
Authentication authResult;
try {
// 很关键!!!调用了子类(UsernamePasswordAuthenticationFilter)的方法
authResult = attemptAuthentication(request, response);
if (authResult == null) {
// return immediately as subclass has indicated that it hasn't completed
// authentication
return;
}
// 最终认证成功后,会处理一些与session相关的方法(比如将认证信息存到session等操作)。
sessionStrategy.onAuthentication(authResult, request, response);
}
catch (InternalAuthenticationServiceException failed) {
logger.error(
"An internal error occurred while trying to authenticate the user.",
failed);
// 认证失败后的一些处理。
unsuccessfulAuthentication(request, response, failed);
return;
}
catch (AuthenticationException failed) {
// Authentication failed
unsuccessfulAuthentication(request, response, failed);
return;
}
// Authentication success
if (continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
/*
* 最终认证成功后的相关回调方法,主要将当前的认证信息放到SecurityContextHolder中
* 并调用成功处理器做相应的操作。
*/
successfulAuthentication(request, response, chain, authResult);
}
}
UsernamePasswordAuthenticationFilter继承了AbstractAuthenticationProcessingFilter,会先执行父类AbstractAuthenticationProcessingFilter,我们主要看dofilter()方法
处理流程就是
1.首先判断当前filter是否可以处理当前请求,若不行,则交给下一个filter去处理。
// 判断当前filter是否可以处理当前请求,若不行,则交给下一个filter去处理
if (!requiresAuthentication(request, response)) {
chain.doFilter(request, response);
return;
}
2.调用了子类UsernamePasswordAuthenticationFilter的attemptAuthentication(request, response)方法
authResult = attemptAuthentication(request, response);
3.对认证的结果进行处理
// 最终认证成功后,会处理一些与session相关的方法(比如将认证信息存到session等操作)。
sessionStrategy.onAuthentication(authResult, request, response);
}
catch (InternalAuthenticationServiceException failed) {
logger.error(
"An internal error occurred while trying to authenticate the user.",
failed);
// 认证失败后的一些处理。
unsuccessfulAuthentication(request, response, failed);
return;
}
catch (AuthenticationException failed) {
// Authentication failed
unsuccessfulAuthentication(request, response, failed);
return;
}
// Authentication success
if (continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
/*
* 最终认证成功后的相关回调方法,主要将当前的认证信息放到SecurityContextHolder中
* 并调用成功处理器做相应的操作。
*/
successfulAuthentication(request, response, chain, authResult);
}
}
子类UsernamePasswordAuthenticationFilter的处理流程是
1.检查请求方式是不是POST方式
// 认证请求的方式必须为POST
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}
2.获得用户名和密码,对用户名和密码进行处理
String username = obtainUsername(request);
String password = obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
3.封装成UsernamePasswordAuthenticationToken,因为此时还没有认证,所以没有权限,false未认证
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
/*public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
super(null);
this.principal = principal;
this.credentials = credentials;
setAuthenticated(false);
}*/
4.最后重要一步,进行认证
return this.getAuthenticationManager().authenticate(authRequest);
调用的是由ProviderManager的authenticate方法,因为ProviderManager实现了AuthenticationManager
public class ProviderManager implements AuthenticationManager, MessageSourceAware,
InitializingBean {
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
Authentication result = null;
boolean debug = logger.isDebugEnabled();
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) {
continue;
}
if (debug) {
logger.debug("Authentication attempt using "
+ provider.getClass().getName());
}
try {
result = provider.authenticate(authentication);
if (result != null) {
copyDetails(authentication, result);
break;
}
}
catch (AccountStatusException e) {
prepareException(e, authentication);
// SEC-546: Avoid polling additional providers if auth failure is due to
// invalid account status
throw e;
}
catch (InternalAuthenticationServiceException e) {
prepareException(e, authentication);
throw e;
}
catch (AuthenticationException e) {
lastException = e;
}
}
if (result == null && parent != null) {
// Allow the parent to try.
try {
result = parent.authenticate(authentication);
}
catch (ProviderNotFoundException e) {
// ignore as we will throw below if no other exception occurred prior to
// calling parent and the parent
// may throw ProviderNotFound even though a provider in the child already
// handled the request
}
catch (AuthenticationException e) {
lastException = e;
}
}
if (result != null) {
if (eraseCredentialsAfterAuthentication
&& (result instanceof CredentialsContainer)) {
// Authentication is complete. Remove credentials and other secret data
// from authentication
((CredentialsContainer) result).eraseCredentials();
}
eventPublisher.publishAuthenticationSuccess(result);
return result;
}
// Parent was null, or didn't authenticate (or throw an exception).
if (lastException == null) {
lastException = new ProviderNotFoundException(messages.getMessage(
"ProviderManager.providerNotFound",
new Object[] { toTest.getName() },
"No AuthenticationProvider found for {0}"));
}
prepareException(lastException, authentication);
throw lastException;
}
ProviderManager的处理流程是
1.遍历所有的provider,看是否支持当前的Authentication
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) {
continue;
}
}
2.如何找到了合适的provider,就调用provider.authenticate(authentication)
try {
result = provider.authenticate(authentication);
if (result != null) {
copyDetails(authentication, result);
break;
}
}
result = provider.authenticate(authentication);这一步是调用了DaoAuthenticationProvider的retrieveUser方法
protected final UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
UserDetails loadedUser;
try {
loadedUser = this.getUserDetailsService().loadUserByUsername(username);
}
catch (UsernameNotFoundException notFound) {
if (authentication.getCredentials() != null) {
String presentedPassword = authentication.getCredentials().toString();
passwordEncoder.isPasswordValid(userNotFoundEncodedPassword,
presentedPassword, null);
}
throw notFound;
}
catch (Exception repositoryProblem) {
throw new InternalAuthenticationServiceException(
repositoryProblem.getMessage(), repositoryProblem);
}
if (loadedUser == null) {
throw new InternalAuthenticationServiceException(
"UserDetailsService returned null, which is an interface contract violation");
}
return loadedUser;
}
retrieveUser中最关键的loadedUser=this.getUserDetailsService().loadUserByUsername(username);
这一步就是在调用我们自己的业务代码
比如:
@Service
public class UserServiceImpl implements UserService, UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException{
return userRepository.findByUsername(username);
}
}
类之间的调用顺序
UsernamePasswordAuthenticationFilter
Authentication
AuthenticationManager
AuthenticationProvider
UserDetailsService
// 回到起点进行后续操作,比如缓存认证信息到session和调用成功后的处理器等等
UsernamePasswordAuthenticationFilter
疑问:
1、接口login在哪定义的?
SpringSecurity内置的,并且只能为POST
public UsernamePasswordAuthenticationFilter() {
super(new AntPathRequestMatcher("/login", "POST"));
}
2、用户名username和密码password在哪接收的?
名称不能变,必须是username和password
public class UsernamePasswordAuthenticationFilter extends
AbstractAuthenticationProcessingFilter {
// ~ Static fields/initializers
// =====================================================================================
public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
}
参考文章https://www.jianshu.com/p/a65f883de0c1