關(guān)于SpringSecurity認(rèn)證邏輯源碼分析
SpringSecurity源碼分析-認(rèn)證邏輯
1. Spring-security-core包中的三個(gè)重要類
SecurityContext
- 這個(gè)類中就兩個(gè)方法getAuthentication()和setAuthentication()
- 這個(gè)類用來存儲(chǔ)Authentication對(duì)象
public interface SecurityContext extends Serializable {
Authentication getAuthentication();
void setAuthentication(Authentication var1);
}Authentication
- 這個(gè)類是貫穿SpringSecurity整個(gè)流程的一個(gè)類。
- 它是一個(gè)接口,它的實(shí)現(xiàn)類中的UsernamePasswordAuthenticationToken是通過用戶名密碼認(rèn)證的實(shí)現(xiàn)
- 登錄成功后用來存儲(chǔ)當(dāng)前的登錄信息。
其中三個(gè)方法:
- getCredentials():獲取當(dāng)前用戶憑證
- getDetails():獲取當(dāng)前登錄用戶詳情
- getPrincipal():獲取當(dāng)前登錄用戶對(duì)象
- isAuthenticated():是否登錄
GrantedAuthority類是用來存儲(chǔ)權(quán)限的,它是一個(gè)接口,常用的SimpleGrantedAuthority實(shí)現(xiàn)類,用來存儲(chǔ)用戶包含的權(quán)限
public interface Authentication extends Principal, Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
Object getCredentials();
Object getDetails();
Object getPrincipal();
boolean isAuthenticated();
void setAuthenticated(boolean var1) throws IllegalArgumentException;
}SecurityContextHolder
- 這個(gè)對(duì)象用來存儲(chǔ)SecurityContext對(duì)象
- 其中有兩個(gè)靜態(tài)方法getContext()和setContext()
- 因此,獲得SecurityContextHolder對(duì)象就能獲得SecurityContext對(duì)象,也就可以獲取Authentication對(duì)象,也就可以獲取當(dāng)前的登錄信息。
- initialize():獲取存儲(chǔ)策略,全局、本地線程、父子線程三種,默認(rèn)本地線程。
public class SecurityContextHolder {
public static SecurityContext getContext() {
return strategy.getContext();
}
private static void initialize() {
if (!StringUtils.hasText(strategyName)) {
strategyName = "MODE_THREADLOCAL";
}
if (strategyName.equals("MODE_THREADLOCAL")) {
strategy = new ThreadLocalSecurityContextHolderStrategy();
} else if (strategyName.equals("MODE_INHERITABLETHREADLOCAL")) {
strategy = new InheritableThreadLocalSecurityContextHolderStrategy();
} else if (strategyName.equals("MODE_GLOBAL")) {
strategy = new GlobalSecurityContextHolderStrategy();
} else {
try {
Class<?> clazz = Class.forName(strategyName);
Constructor<?> customStrategy = clazz.getConstructor();
strategy = (SecurityContextHolderStrategy)customStrategy.newInstance();
} catch (Exception var2) {
ReflectionUtils.handleReflectionException(var2);
}
}
++initializeCount;
}
public static void setContext(SecurityContext context) {
strategy.setContext(context);
}
}小結(jié):Authentication用來存儲(chǔ)認(rèn)證信息,SecurityContext用來存儲(chǔ)認(rèn)證信息的容器,SecurityContextHolder用來定義容器的存儲(chǔ)策略。
2. 基于用戶名密碼認(rèn)證的流程
找到UsernamePasswordAuthenticationFilter,找到doFilter方法
- 發(fā)現(xiàn)沒有doFilter方法,去父類AbstractAuthenticationProcessingFilter中查看
- 其實(shí)是這樣一個(gè)邏輯
- 所有AbstractAuthenticationProcessingFilter的實(shí)現(xiàn)類都調(diào)用父類中的doFilter方法
- 在doFilter方法中調(diào)用了attemptAuthentication方法
- attemptAuthentication方法是一個(gè)抽象方法,子類去實(shí)現(xiàn)AbstractAuthenticationProcessingFilter抽象方法
UsernamePasswordAuthenticationFilter類
public class UsernamePasswordAuthenticationFilter extends
AbstractAuthenticationProcessingFilter {
//…… ……
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();
//封裝成Authentication對(duì)象
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
setDetails(request, authRequest);
//認(rèn)證操作,ProviderManager中執(zhí)行
return this.getAuthenticationManager().authenticate(authRequest);
}
//…… ……
}AbstractAuthenticationProcessingFilter類
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 (!requiresAuthentication(request, response)) {
chain.doFilter(request, response);
return;
}
if (logger.isDebugEnabled()) {
logger.debug("Request is to process authentication");
}
Authentication authResult;
try {
//認(rèn)證邏輯
authResult = attemptAuthentication(request, response);
if (authResult == null) {
// return immediately as subclass has indicated that it hasn't completed
// authentication
return;
}
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);
}
//認(rèn)證成功后的邏輯.......
successfulAuthentication(request, response, chain, authResult);
}
//認(rèn)證邏輯的抽象方法,交給子類去實(shí)現(xiàn)
public abstract Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException, IOException,
ServletException;
}查看子類UsernamePasswordAuthenticationFilter中的attemptAuthentication方法
- 認(rèn)證的邏輯在這里: this.getAuthenticationManager().authenticate(authRequest)
- 認(rèn)證完畢后封裝Authentication對(duì)象,返回。
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();
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}ProviderManager類中的authenticate方法
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();
Iterator var8 = this.getProviders().iterator();
while(var8.hasNext()) {
AuthenticationProvider provider = (AuthenticationProvider)var8.next();
if (provider.supports(toTest)) {
if (debug) {
logger.debug("Authentication attempt using " + provider.getClass().getName());
}
try {
// 認(rèn)證邏輯
result = provider.authenticate(authentication);
if (result != null) {
this.copyDetails(authentication, result);
break;
}
} catch (AccountStatusException var13) {
this.prepareException(var13, authentication);
throw var13;
} catch (InternalAuthenticationServiceException var14) {
this.prepareException(var14, authentication);
throw var14;
} catch (AuthenticationException var15) {
lastException = var15;
}
}
}
if (result == null && this.parent != null) {
try {
result = parentResult = this.parent.authenticate(authentication);
} catch (ProviderNotFoundException var11) {
} catch (AuthenticationException var12) {
parentException = var12;
lastException = var12;
}
}
if (result != null) {
if (this.eraseCredentialsAfterAuthentication && result instanceof CredentialsContainer) {
((CredentialsContainer)result).eraseCredentials();
}
if (parentResult == null) {
this.eventPublisher.publishAuthenticationSuccess(result);
}
return result;
} else {
if (lastException == null) {
lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound", new Object[]{toTest.getName()}, "No AuthenticationProvider found for {0}"));
}
if (parentException == null) {
this.prepareException((AuthenticationException)lastException, authentication);
}
throw lastException;
}
}AbstractUserDetailsAuthenticationProvider中的Authentication方法
public Authentication authenticate(Authentication authentication)
throws AuthenticationException{
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class,authentication,
()->messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.onlySupports",
"Only UsernamePasswordAuthenticationToken is supported"));
// 獲取用戶名
String username=(authentication.getPrincipal()==null)?"NONE_PROVIDED"
:authentication.getName();
boolean cacheWasUsed=true;
//緩存獲取user
UserDetails user=this.userCache.getUserFromCache(username);
if(user==null){
cacheWasUsed=false;
try{
//自定義獲取user,一般從數(shù)據(jù)庫讀取
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{
preAuthenticationChecks.check(user);
//去比對(duì)密碼
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{
throw exception;
}
}
postAuthenticationChecks.check(user);
if(!cacheWasUsed){
this.userCache.putUserInCache(user);
}
Object principalToReturn=user;
if(forcePrincipalAsString){
principalToReturn=user.getUsername();
}
//封裝Authentication對(duì)象
return createSuccessAuthentication(principalToReturn,authentication,user);
}
DaoAuthenticationProvider中的retrieveUser方法
protected final UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
prepareTimingAttackProtection();
try {
//調(diào)用我們自己的loadUserByUsername方法獲取user
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 new InternalAuthenticationServiceException(ex.getMessage(), ex);
}
}小結(jié):至此返回Authentication對(duì)象完成認(rèn)證
3. 認(rèn)證成功后的邏輯
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response, FilterChain chain, Authentication authResult)
throws IOException, ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Authentication success. Updating SecurityContextHolder to contain: "
+ authResult);
}
//將認(rèn)證后的對(duì)象放到SecurityContext中
SecurityContextHolder.getContext().setAuthentication(authResult);
//記住我的執(zhí)行邏輯
rememberMeServices.loginSuccess(request, response, authResult);
// 發(fā)布認(rèn)證成功后的時(shí)間,可以自定義監(jiān)聽器
if (this.eventPublisher != null) {
eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
authResult, this.getClass()));
}
//認(rèn)證成功后的執(zhí)行邏輯,默認(rèn)三種,可以通過實(shí)現(xiàn)AuthenticationSuccessHandler接口自定義認(rèn)證成功后邏輯
successHandler.onAuthenticationSuccess(request, response, authResult);
}4. 記住我是如何實(shí)現(xiàn)的
AbstractRememberMeServices的loginSuccess方法和rememberMeRequested方法
public final void loginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) {
//判斷是否勾選remember
if (!this.rememberMeRequested(request, this.parameter)) {
this.logger.debug("Remember-me login not requested.");
} else {
this.onLoginSuccess(request, response, successfulAuthentication);
}
}
protected boolean rememberMeRequested(HttpServletRequest request, String parameter) {
if (this.alwaysRemember) {
return true;
} else {
String paramValue = request.getParameter(parameter);
//判斷是否勾選remember,傳入的值可以為以下內(nèi)容
if (paramValue != null && (paramValue.equalsIgnoreCase("true") || paramValue.equalsIgnoreCase("on") || paramValue.equalsIgnoreCase("yes") || paramValue.equals("1"))) {
return true;
} else {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Did not send remember-me cookie (principal did not set parameter '" + parameter + "')");
}
return false;
}
}
}PersistentTokenBasedRememberMeServices中的onLoginSuccess方法完成持久化操作
protected void onLoginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) {
String username = successfulAuthentication.getName();
this.logger.debug("Creating new persistent login for user " + username);
PersistentRememberMeToken persistentToken = new PersistentRememberMeToken(username, this.generateSeriesData(), this.generateTokenData(), new Date());
try {
//持久化操作
this.tokenRepository.createNewToken(persistentToken);
//將token放到cookie中,可以自定義
this.addCookie(persistentToken, request, response);
} catch (Exception var7) {
this.logger.error("Failed to save persistent token ", var7);
}
}持久化操作有兩個(gè)實(shí)現(xiàn)JdbcTokenRepositoryImpl存到數(shù)據(jù)庫,InMemoryTokenRepositoryImpl存到內(nèi)存中
5.Security中的ExceptionTranslationFilter過濾器
- 這個(gè)過濾器不處理邏輯
- 只捕獲Security中的異常
- 捕獲異常后處理異常信息
public class ExceptionTranslationFilter extends GenericFilterBean {
// ~ Instance fields
// ================================================================================================
private AccessDeniedHandler accessDeniedHandler = new AccessDeniedHandlerImpl();
private AuthenticationEntryPoint authenticationEntryPoint;
private AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl();
private ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer();
private RequestCache requestCache = new HttpSessionRequestCache();
private final MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
public ExceptionTranslationFilter(AuthenticationEntryPoint authenticationEntryPoint) {
this(authenticationEntryPoint, new HttpSessionRequestCache());
}
public ExceptionTranslationFilter(AuthenticationEntryPoint authenticationEntryPoint,
RequestCache requestCache) {
Assert.notNull(authenticationEntryPoint,
"authenticationEntryPoint cannot be null");
Assert.notNull(requestCache, "requestCache cannot be null");
this.authenticationEntryPoint = authenticationEntryPoint;
this.requestCache = requestCache;
}
// ~ Methods
// ========================================================================================================
@Override
public void afterPropertiesSet() {
Assert.notNull(authenticationEntryPoint,
"authenticationEntryPoint must be specified");
}
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
try {
/** 交給下一個(gè)過濾器處理*/
chain.doFilter(request, response);
logger.debug("Chain processed normally");
}
catch (IOException ex) {
throw ex;
}
catch (Exception ex) {
/** 捕獲Security中的異常,處理異常*/
Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);
RuntimeException ase = (AuthenticationException) throwableAnalyzer
.getFirstThrowableOfType(AuthenticationException.class, causeChain);
if (ase == null) {
ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType(
AccessDeniedException.class, causeChain);
}
if (ase != null) {
if (response.isCommitted()) {
throw new ServletException("Unable to handle the Spring Security Exception because the response is already committed.", ex);
}
/** 處理異常*/
handleSpringSecurityException(request, response, chain, ase);
}
else {
// Rethrow ServletExceptions and RuntimeExceptions as-is
if (ex instanceof ServletException) {
throw (ServletException) ex;
}
else if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
}
// Wrap other Exceptions. This shouldn't actually happen
// as we've already covered all the possibilities for doFilter
throw new RuntimeException(ex);
}
}
}
public AuthenticationEntryPoint getAuthenticationEntryPoint() {
return authenticationEntryPoint;
}
protected AuthenticationTrustResolver getAuthenticationTrustResolver() {
return authenticationTrustResolver;
}
private void handleSpringSecurityException(HttpServletRequest request,
HttpServletResponse response, FilterChain chain, RuntimeException exception)
throws IOException, ServletException {
/** 根據(jù)不同的異常,做出不同的處理*/
if (exception instanceof AuthenticationException) {
logger.debug(
"Authentication exception occurred; redirecting to authentication entry point",
exception);
sendStartAuthentication(request, response, chain,
(AuthenticationException) exception);
}
else if (exception instanceof AccessDeniedException) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authenticationTrustResolver.isAnonymous(authentication) || authenticationTrustResolver.isRememberMe(authentication)) {
logger.debug(
"Access is denied (user is " + (authenticationTrustResolver.isAnonymous(authentication) ? "anonymous" : "not fully authenticated") + "); redirecting to authentication entry point",
exception);
sendStartAuthentication(
request,
response,
chain,
new InsufficientAuthenticationException(
messages.getMessage(
"ExceptionTranslationFilter.insufficientAuthentication",
"Full authentication is required to access this resource")));
}
else {
logger.debug(
"Access is denied (user is not anonymous); delegating to AccessDeniedHandler",
exception);
accessDeniedHandler.handle(request, response,
(AccessDeniedException) exception);
}
}
}
protected void sendStartAuthentication(HttpServletRequest request,
HttpServletResponse response, FilterChain chain,
AuthenticationException reason) throws ServletException, IOException {
// SEC-112: Clear the SecurityContextHolder's Authentication, as the
// existing Authentication is no longer considered valid
SecurityContextHolder.getContext().setAuthentication(null);
requestCache.saveRequest(request, response);
logger.debug("Calling Authentication entry point.");
authenticationEntryPoint.commence(request, response, reason);
}
public void setAccessDeniedHandler(AccessDeniedHandler accessDeniedHandler) {
Assert.notNull(accessDeniedHandler, "AccessDeniedHandler required");
this.accessDeniedHandler = accessDeniedHandler;
}
public void setAuthenticationTrustResolver(
AuthenticationTrustResolver authenticationTrustResolver) {
Assert.notNull(authenticationTrustResolver,
"authenticationTrustResolver must not be null");
this.authenticationTrustResolver = authenticationTrustResolver;
}
public void setThrowableAnalyzer(ThrowableAnalyzer throwableAnalyzer) {
Assert.notNull(throwableAnalyzer, "throwableAnalyzer must not be null");
this.throwableAnalyzer = throwableAnalyzer;
}
/**
* Default implementation of <code>ThrowableAnalyzer</code> which is capable of also
* unwrapping <code>ServletException</code>s.
*/
private static final class DefaultThrowableAnalyzer extends ThrowableAnalyzer {
/**
* @see org.springframework.security.web.util.ThrowableAnalyzer#initExtractorMap()
*/
protected void initExtractorMap() {
super.initExtractorMap();
registerExtractor(ServletException.class, new ThrowableCauseExtractor() {
public Throwable extractCause(Throwable throwable) {
ThrowableAnalyzer.verifyThrowableHierarchy(throwable,
ServletException.class);
return ((ServletException) throwable).getRootCause();
}
});
}
}
}6.登錄頁面是如何產(chǎn)生的
答案在最后一個(gè)過濾器DefaultLoginPageGeneratingFilter中
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
boolean loginError = this.isErrorPage(request);
boolean logoutSuccess = this.isLogoutSuccess(request);
if (!this.isLoginUrlRequest(request) && !loginError && !logoutSuccess) {
chain.doFilter(request, response);
} else {
//拼接生產(chǎn)html登錄頁面
String loginPageHtml = this.generateLoginPageHtml(request, loginError, logoutSuccess);
response.setContentType("text/html;charset=UTF-8");
response.setContentLength(loginPageHtml.getBytes(StandardCharsets.UTF_8).length);
response.getWriter().write(loginPageHtml);
}
}總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Future與FutureTask接口實(shí)現(xiàn)示例詳解
這篇文章主要為大家介紹了Future與FutureTask接口實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10
springboot項(xiàng)目不加端口號(hào)也可以訪問項(xiàng)目的方法步驟分析
這篇文章主要介紹了springboot項(xiàng)目不加端口號(hào)也可以訪問項(xiàng)目的方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-04-04
Spring中的@ExceptionHandler注解詳解與應(yīng)用示例
本文詳細(xì)介紹了Spring框架中的@ExceptionHandler注解的用法,包括基本用法、全局異常處理、結(jié)合@ResponseStatus注解以及返回值類型,通過示例展示了如何使用@ExceptionHandler注解處理不同類型的異常,并提供定制化的異常處理響應(yīng),需要的朋友可以參考下2024-11-11
Oracle+Mybatis的foreach insert批量插入報(bào)錯(cuò)的快速解決辦法
本文給大家介紹Oracle+Mybatis的foreach insert批量插入報(bào)錯(cuò)的快速解決辦法,非常不錯(cuò),具有參考借鑒價(jià)值,感興趣的朋友參考下吧2016-08-08
mybatisPlus自動(dòng)填充更新時(shí)間的示例代碼
本文主要介紹了mybatisPlus自動(dòng)填充更新時(shí)間,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09

