Overall process of authentication and authorization
Start with a self-organized flow chart of Spring Security authentication and authorization. This chapter, mainly according to the process, simple walking comb source code.
Certified source code analysis
AbstractAuthenticationProcessingFilter, this class is the entrance of the certification process.
Create the Authentication AbstractAuthenticationProcessingFilter according to the Request.
The Authentication specific types, determined by the actual use AbstractAuthenticationProcessingFilter child class. Common are: UsernamePasswordAuthenticationToken.
By default, using UsernamePasswordAuthenticationFilter classes, so the corresponding Authentication is a UsernamePasswordAuthenticationToken.
UsernamePasswordAuthenticationFilter# attemptAuthentication, / /… Indicates that some secondary logic is deleted.
/ / UsernamePasswordAuthenticationFilter# attemptAuthentication ` / /... 'indicates that some secondary logic is deleted.
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
/ /...
String username = obtainUsername(request);
String password = obtainPassword(request);
/ /...
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
Copy the code
We can see that the AuthenticationManager is called in the last sentence above. From the official documentation, we know that ProviderManager is the default implementation of AuthenticationManager. So we need to go to the ProviderManager.
Support provider is DaoAuthenticationProvider UsernamePasswordAuthenticationToken, DaoAuthenticationProvider inherited from AbstractUserDetailsAuthenticationProvider.
ProviderManager#authenticate
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
/ / for Autentication type, this is we are actually UsernamePasswordAuthenticationToken
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
AuthenticationException parentException = null;
Authentication result = null;
Authentication parentResult = null;
// Get all authenticationProviders
for (AuthenticationProvider provider : getProviders()) {
// Determine whether the provider supports the Autentication
/ / here DaoAuthenticationProvider through verification
if(! provider.supports(toTest)) {continue;
}
/ /...
try {
/ / perform authentication AbstractUserDetailsAuthenticationProvider# authenticate
result = provider.authenticate(authentication);
if(result ! =null) {
copyDetails(authentication, result);
break; }}/ /...
}
/ /.....................
if(result ! =null) {
if (eraseCredentialsAfterAuthentication
&& (result instanceof CredentialsContainer)) {
// Authentication is complete. Remove credentials and other secret data
// from authentication
((CredentialsContainer) result).eraseCredentials();
}
/ /.....................................................................
/ /.....................................................................
}
Copy the code
AbstractUserDetailsAuthenticationProvider# supports, authenticate
public boolean supports(Class
authentication) {
return (UsernamePasswordAuthenticationToken.class
.isAssignableFrom(authentication));
}
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
/ /...
// Determine username
// Get the user name
String username = (authentication.getPrincipal() == null)?"NONE_PROVIDED"
: authentication.getName();
boolean cacheWasUsed = true;
// Get the user body based on the user name
UserDetails user = this.userCache.getUserFromCache(username);
if (user == null) {
cacheWasUsed = false;
try {
// Not in cache, call the retrieveUser method to get the user
user = retrieveUser(username,
(UsernamePasswordAuthenticationToken) authentication);
}
catch (UsernameNotFoundException notFound) {
/ /...
}
/ /...
/ /...
return createSuccessAuthentication(principalToReturn, authentication, user);
}
Copy the code
DaoAuthenticationProvider# retrieveUser, createSuccessAuthentication
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
prepareTimingAttackProtection();
try {
// if there is no user in memory, call UserDeatilsService#loadUserByUsername to get the user
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
if (loadedUser == null) {
throw new InternalAuthenticationServiceException(
"UserDetailsService returned null, which is an interface contract violation");
}
return loadedUser;
}
/ /.....................
}
@Override
protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {
// Verify password
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);
}
// super.createSuccessAuthentication
protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {
/ / create a UsernamePasswordAuthenticationToken returns
UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
principal, authentication.getCredentials(),
authoritiesMapper.mapAuthorities(user.getAuthorities()));
result.setDetails(authentication.getDetails());
return result;
}
Copy the code
Authorized source code analysis
Unauthorized entry is FilterSecurityInterceptor.
FilterSecurityInterceptor
/ /...
private FilterInvocationSecurityMetadataSource securityMetadataSource;
/ /...
public void invoke(FilterInvocation fi) throws IOException, ServletException {
/ /...
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
finally {
super.finallyInvocation(token);
}
super.afterInvocation(token, null); }}/ /...
Copy the code
This shows that Spring Security is the default SecurityMetadataSource FilterInvocationSecurityMetadataSource.
We observed that the super-.beforeInvocation () goes to its parent class and continues the process.
AbstractSecurityInterceptor
protected InterceptorStatusToken beforeInvocation(Object object) {
Assert.notNull(object, "Object was null");
/ /...
/ / call FilterInvocationSecurityMetadataSource here, to get access to the current path needs to be set
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
.getAttributes(object);
// If the value is empty, the authentication process is not continued and the system returns directly
if (attributes == null || attributes.isEmpty()) {
/ /...
publishEvent(new PublicInvocationEvent(object));
return null; // no further work post-invocation
}
/ /...
// If there is no authentication, an exception is thrown
if (SecurityContextHolder.getContext().getAuthentication() == null) {
credentialsNotFound(messages.getMessage(
"AbstractSecurityInterceptor.authenticationNotFound"."An Authentication object was not found in the SecurityContext"),
object, attributes);
}
/ /...
AccessDecisionManager authorization is invoked here, and an exception is thrown if permissions are insufficient.
try {
this.accessDecisionManager.decide(authenticated, object, attributes);
}
catch (AccessDeniedException accessDeniedException) {
publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
accessDeniedException));
throw accessDeniedException;
}
/ /...
/ /...
}
Copy the code
AccessDecisionManager is an interface. As can be seen from the log, Spring Security uses its implementation class AffirmativeBased by default and WebExpressionVoter is also used.
o.s.s.access.vote.AffirmativeBased : Voter: org.springframework.security.web.access.expression.WebExpressionVoter@bb39abe, returned: -1
Copy the code
AffirmativeBased#decide
public void decide(Authentication authentication, Object object, Collection
configAttributes)
throws AccessDeniedException {
int deny = 0;
for (AccessDecisionVoter voter : getDecisionVoters()) {
// Handle authorization
int result = voter.vote(authentication, object, configAttributes);
/ /...
switch (result) {
case AccessDecisionVoter.ACCESS_GRANTED:
return;
case AccessDecisionVoter.ACCESS_DENIED:
deny++;
break;
default:
break; }}// If the authorization fails, add one. At this point, if greater than 0, an exception is thrown, otherwise normal.
if (deny > 0) {
throw new AccessDeniedException(messages.getMessage(
"AbstractAccessDecisionManager.accessDenied"."Access is denied"));
}
/ /...
}
Copy the code
The actual authentication is done in matter.vote (). According to the log, it is handled by its implementation class WebExpressionVoter.
WebExpressionVoter# vote, findConfigAttribute
public int vote(Authentication authentication, FilterInvocation fi, Collection
attributes)
{ assertauthentication ! =null; assertfi ! =null; assertattributes ! =null; / / the attributes into WebExpressionConfigAttribute WebExpressionConfigAttribute weca = findConfigAttribute (attributes); // If (weca == null) {return ACCESS_ABSTAIN; } / / to create an environment determine EvaluationContext CTX. = expressionHandler createEvaluationContext (authentication, fi); ctx = weca.postProcess(ctx, fi); / / execution decision return ExpressionUtils. EvaluateAsBoolean (weca. GetAuthorizeExpression (), CTX)? ACCESS_GRANTED : ACCESS_DENIED; } / / WebExpressionConfigAttribute attribute translate into, Matching to it returns null if no private WebExpressionConfigAttribute findConfigAttribute (Collection < ConfigAttribute > attributes) {the for (ConfigAttribute attribute : attributes) { if (attribute instanceof WebExpressionConfigAttribute) { return (WebExpressionConfigAttribute) attribute; } } return null; }
Copy the code
This is the end of the authentication.