The < Core filter loading > article has been analyzed,FilterChainProxy
Once you get the filter chain, start walking through the logic of the filter chain. As is shown in
These filter chains are included as shown below
The < Core Filter introduction > section shows that the authentication logic for form authentication is mainly inUsernamePasswordAuthenticationFilter
So let’s start analyzingUsernamePasswordAuthenticationFilter
Let’s look at its parent class before we look at it
public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean
implements ApplicationEventPublisherAware.MessageSourceAware {
// ~ Static fields/initializers
/ / = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
// ~ Instance fields
/ / = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
protected ApplicationEventPublisher eventPublisher;
protectedAuthenticationDetailsSource<HttpServletRequest, ? > authenticationDetailsSource =new WebAuthenticationDetailsSource();
private AuthenticationManager authenticationManager;
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
private RememberMeServices rememberMeServices = new NullRememberMeServices();
private RequestMatcher requiresAuthenticationRequestMatcher;
private boolean continueChainBeforeSuccessfulAuthentication = false;
private SessionAuthenticationStrategy sessionStrategy = new NullAuthenticatedSessionStrategy();
private boolean allowSessionCreation = true;
private AuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
private AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();
// Filter authentication entry
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
// Whether this filter validates login requests
if(! requiresAuthentication(request, response)) {// If no verification, skip
chain.doFilter(request, response);
return;
}
Authentication authResult;
try {
// Call subclasses to get the Authentication object (successful Authentication information such as username, password, etc.)
authResult = attemptAuthentication(request, response);
if (authResult == null) {
// Is empty
return;
}
sessionStrategy.onAuthentication(authResult, request, response);
}
catch (InternalAuthenticationServiceException failed) {
logger.error(
"An internal error occurred while trying to authenticate the user.",
failed);
// Handle exceptions
unsuccessfulAuthentication(request, response, failed);
return;
}
catch (AuthenticationException failed) {
// // Handling exceptions
unsuccessfulAuthentication(request, response, failed);
return;
}
if (continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
// Authentication success logic, such as save context information, jump to login success page
successfulAuthentication(request, response, chain, authResult);
}
protected boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) {
return requiresAuthenticationRequestMatcher.matches(request);
}
// Subclass authentication logic
public abstract Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException,
ServletException;
}
Copy the code
We see the main work of the parent class in the source code
- See if the current filter blocks login requests
- Invoke the authentication logic of the subclass
- The login succeeds or fails based on the authentication result of the subclass
Let’s look at the authentication logic for subclasses
public class UsernamePasswordAuthenticationFilter extends
AbstractAuthenticationProcessingFilter {
// ~ Static fields/initializers
/ / = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
// The user name and password receive parameters are predefined
public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
private boolean postOnly = true;
// ~ Constructors
/ / = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
public UsernamePasswordAuthenticationFilter(a) {
// Login authentication path
super(new AntPathRequestMatcher("/login"."POST"));
}
// ~ Methods
/ / = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
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();
/ / to encapsulate it into UsernamePasswordAuthenticationToken
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
// Get the authentication manager for authentication
return this.getAuthenticationManager().authenticate(authRequest); }}Copy the code
So, subclasses mainly do:
- Get the username, password, and encapsulate it as
UsernamePasswordAuthenticationToken
- Gets the authentication manager into which the token is passed for authentication
We look at how the Authentication manager work, but before that we should see the Authentication, and has UsernamePasswordAuthenticationToken is a what thing (PS: < SpringSecurity core concepts had been simple introduction >), The AuthenticationManager is also introduced in the
. Its implementation class is usually ProviderManager, So let’s look at ProviderManager source code how to achieve
public class ProviderManager implements AuthenticationManager.MessageSourceAware.InitializingBean {
// ~ Static fields/initializers
/ / = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
private static final Log logger = LogFactory.getLog(ProviderManager.class);
// ~ Instance fields
/ / = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
private AuthenticationEventPublisher eventPublisher = new NullEventPublisher();
private List<AuthenticationProvider> providers = Collections.emptyList();
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
private AuthenticationManager parent;
private boolean eraseCredentialsAfterAuthentication = true;
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();
// Get the authentication provider
for (AuthenticationProvider provider : getProviders()) {
if(! provider.supports(toTest)) {continue;
}
try {
// Perform authentication
result = provider.authenticate(authentication);
if(result ! =null) {
copyDetails(authentication, result);
break; }}/ / has a problem throw exceptions, by ExceptionTransactionFilter processing
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 = parentResult = 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 = parentException = e; }}if(result ! =null) {
if (eraseCredentialsAfterAuthentication
&& (result instanceof CredentialsContainer)) {
// Authentication is complete. Remove credentials and other secret data
// from authentication
((CredentialsContainer) result).eraseCredentials();
}
// If the parent AuthenticationManager was attempted and successful than it will publish an AuthenticationSuccessEvent
// This check prevents a duplicate AuthenticationSuccessEvent if the parent AuthenticationManager already published it
if (parentResult == null) {
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}"));
}
// If the parent AuthenticationManager was attempted and failed than it will publish an AbstractAuthenticationFailureEvent
// This check prevents a duplicate AbstractAuthenticationFailureEvent if the parent AuthenticationManager already published it
if (parentException == null) {
prepareException(lastException, authentication);
}
throwlastException; }}Copy the code
And we know from the code,ProviderManager
It’s going to go through all of themAuthenticationProvider collection,
Real authentication logic byAuthenticationProvider
To do
Let’s keep goingAuthenticationProvider
The implementation of the class
Let’s pick a common implementation classDaoAuthenticationProvider
From its name, we know that this class is related to a database. We’re going to analyze his superclass as we did before
public abstract class AbstractUserDetailsAuthenticationProvider implements
AuthenticationProvider.InitializingBean.MessageSourceAware {
protected final Log logger = LogFactory.getLog(getClass());
// ~ Instance fields
/ / = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
private UserCache userCache = new NullUserCache();
private boolean forcePrincipalAsString = false;
protected boolean hideUserNotFoundExceptions = true;
private UserDetailsChecker preAuthenticationChecks = new DefaultPreAuthenticationChecks();
private UserDetailsChecker postAuthenticationChecks = new DefaultPostAuthenticationChecks();
private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper();
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
() -> messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.onlySupports"."Only UsernamePasswordAuthenticationToken is supported"));
// Determine username
String username = (authentication.getPrincipal() == null)?"NONE_PROVIDED"
: authentication.getName();
boolean cacheWasUsed = true;
// Retrieve user information from the cache
UserDetails user = this.userCache.getUserFromCache(username);
if (user == null) {
cacheWasUsed = false;
try {
// Re-pull the user information, such as from the database, there are subclasses
user = retrieveUser(username,
(UsernamePasswordAuthenticationToken) authentication);
}
catch (UsernameNotFoundException notFound) {
logger.debug("User '" + username + "' not found");
if (hideUserNotFoundExceptions) {
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials"."Bad credentials"));
}
else {
throw notFound;
}
}
Assert.notNull(user,
"retrieveUser returned null - a violation of the interface contract");
}
try {
// Determine whether the account is locked, unavailable, etc
preAuthenticationChecks.check(user);
// Password comparison is implemented by subclasses
additionalAuthenticationChecks(user,
(UsernamePasswordAuthenticationToken) authentication);
}
catch (AuthenticationException exception) {
if (cacheWasUsed) {
// There was a problem, so try again after checking
// we're using latest data (i.e. not from the cache)
cacheWasUsed = false;
user = retrieveUser(username,
(UsernamePasswordAuthenticationToken) authentication);
preAuthenticationChecks.check(user);
additionalAuthenticationChecks(user,
(UsernamePasswordAuthenticationToken) authentication);
}
else {
throwexception; }}// Password expiration verification
postAuthenticationChecks.check(user);
if(! cacheWasUsed) {this.userCache.putUserInCache(user);
}
Object principalToReturn = user;
if (forcePrincipalAsString) {
principalToReturn = user.getUsername();
}
// No problem, return an Authentication object
returncreateSuccessAuthentication(principalToReturn, authentication, user); }}Copy the code
So let’s go through the logic of the parent class
- Get user information from the cache. The default implementation class is
NullUserCache
, returns null - Get user information from the logic written by the subclass
- Do some pre-validation of user information
- Do password comparison, this part is also implemented by subclasses
- Perform post-verification on user information
- No problem, return one
Authentication
object
Let’s look at class logic again
public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
// ~ Static fields/initializers
/ / = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
/** * The plaintext password used to perform * PasswordEncoder#matches(CharSequence, String)} on when the user is * not found to avoid SEC-2056. */
private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";
// ~ Instance fields
/ / = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
private PasswordEncoder passwordEncoder;
/**
* The password used to perform
* {@link PasswordEncoder#matches(CharSequence, String)} on when the user is
* not found to avoid SEC-2056. This is necessary, because some
* {@link PasswordEncoder} implementations will short circuit if the password is not
* in a valid format.
*/
private volatile String userNotFoundEncodedPassword;
// The implementation logic class for user information retrieval
private UserDetailsService userDetailsService;
private UserDetailsPasswordService userDetailsPasswordService;
public DaoAuthenticationProvider(a) {
setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
}
// ~ Methods
/ / = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
@SuppressWarnings("deprecation")
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
if (authentication.getCredentials() == null) {
logger.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials"."Bad credentials"));
}
String presentedPassword = authentication.getCredentials().toString();
// Password comparison
if(! passwordEncoder.matches(presentedPassword, userDetails.getPassword())) { logger.debug("Authentication failed: password does not match stored value");
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials"."Bad credentials")); }}protected void doAfterPropertiesSet(a) throws Exception {
Assert.notNull(this.userDetailsService, "A UserDetailsService must be set");
}
// Get user information
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
prepareTimingAttackProtection();
try {
// Get user information
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
if (loadedUser == null) {
throw new InternalAuthenticationServiceException(
"UserDetailsService returned null, which is an interface contract violation");
}
return loadedUser;
}
catch (UsernameNotFoundException ex) {
mitigateAgainstTimingAttack(authentication);
throw ex;
}
catch (InternalAuthenticationServiceException ex) {
throw ex;
}
catch (Exception ex) {
throw newInternalAuthenticationServiceException(ex.getMessage(), ex); }}@Override
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); }}Copy the code
JdbcDaoImpl, loadUserByUsername, loadUserByUsername, loadUserByUsername
@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
List<UserDetails> users = loadUsersByUsername(username);
if (users.size() == 0) {
this.logger.debug("Query returned no results for user '" + username + "'");
throw new UsernameNotFoundException(
this.messages.getMessage("JdbcDaoImpl.notFound".new Object[] { username }, "Username {0} not found"));
}
// Get user information
UserDetails user = users.get(0); // contains no GrantedAuthority[]
Set<GrantedAuthority> dbAuthsSet = new HashSet<>();
if (this.enableAuthorities) {
dbAuthsSet.addAll(loadUserAuthorities(user.getUsername()));
}
if (this.enableGroups) {
dbAuthsSet.addAll(loadGroupAuthorities(user.getUsername()));
}
List<GrantedAuthority> dbAuths = new ArrayList<>(dbAuthsSet);
addCustomAuthorities(user.getUsername(), dbAuths);
if (dbAuths.size() == 0) {
this.logger.debug("User '" + username
+ "' has no authorities and will be treated as 'not found'");
throw new UsernameNotFoundException(this.messages.getMessage(
"JdbcDaoImpl.noAuthority".new Object[] { username },
"User {0} has no GrantedAuthority"));
}
return createUserDetails(username, user, dbAuths);
}
// Find user information from the database
protected List<UserDetails> loadUsersByUsername(String username) {
return getJdbcTemplate().query(this.usersByUsernameQuery,
new String[] { username }, new RowMapper<UserDetails>() {
@Override
public UserDetails mapRow(ResultSet rs, int rowNum)
throws SQLException {
String username = rs.getString(1);
String password = rs.getString(2);
boolean enabled = rs.getBoolean(3);
return new User(username, password, enabled, true.true.true, AuthorityUtils.NO_AUTHORITIES); }}); }// Create the User object
protected UserDetails createUserDetails(String username, UserDetails userFromUserQuery, List
combinedAuthorities)
{
String returnUsername = userFromUserQuery.getUsername();
if (!this.usernameBasedPrimaryKey) {
returnUsername = username;
}
return new User(returnUsername, userFromUserQuery.getPassword(),
userFromUserQuery.isEnabled(), true.true.true, combinedAuthorities);
}
Copy the code
So JdbcDaoImpl will retrieve the User name, password, permissions and other information from the database to form a User object and return it to the UserDetailsService. So we can emulate Spring and implement a UserDetailsService ourselves to meet certain business requirements
@Service
@Transactional
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Autowired
private RoleService roleService;
@Override
public void save(SysUser user) {
userDao.save(user);
}
@Override
public List<SysUser> findAll(a) {
return userDao.findAll();
}
@Override
public Map<String, Object> toAddRolePage(Integer id) {
List<SysRole> allRoles = roleService.findAll();
List<Integer> myRoles = userDao.findRolesByUid(id);
Map<String, Object> map = new HashMap<>();
map.put("allRoles", allRoles);
map.put("myRoles", myRoles);
return map;
}
@Override
public void addRoleToUser(Integer userId, Integer[] ids) {
userDao.removeRoles(userId);
for(Integer rid : ids) { userDao.addRoles(userId, rid); }}@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
SysUser sysUser = userDao.findByName(username);
if (sysUser == null) {
return null;
}
List<SimpleGrantedAuthority> list = new ArrayList<>();
List<SysRole> roles = sysUser.getRoles();
List<SimpleGrantedAuthority> collect = roles.stream()
.map(SysRole::getRoleName)
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
// {noop} do not encrypt authentication
UserDetails userDetails = new User(username, sysUser.getPassword(),
sysUser.getStatus() == 1.true.true.true,
collect);
// UserDetails userDetails = new User(username, "{noop}"+sysUser.getPassword(), collect);
returnuserDetails; }}Copy the code
This class is then added to the Security configuration class
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserService userService
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService);
}
// ...
}
Copy the code
Reference:
- Spring Security source analysis a: Spring Security authentication process
- SpringSecurity