SpringSecurity主要对象及认证流程
- 前言
- 一、Servlet Security
- 二、认证流程分析
-
- 2.1登录流程
- 2.2UsernamePasswordAuthenticationFilter
- 2.3AuthenticationManager
- 2.4AbstractUserDetailsAuthenticationProvider
- 2.5UserDetailsService
- 2.6AbstractUserDetailsAuthenticationProvider中authenticate返回值
- 2.7UsernamePasswordAuthenticationToken
- 2.8AbstractAuthenticationToken
- 2.9AbstractAuthenticationProcessingFilter
- 三、自定义实现认证功能
- 四、加密认证
- 总结
前言
SpringSecurity是一款十分强大的安全框架,它主要具有认证、授权、安全保护等功能。接下来,我将详细讲述SpringSecurity是如何在Web项目中运行,并结合表单登录讲解其认证流程。
一、Servlet Security
1.1过滤器及过滤器链的概念
Java中的Filter并不是一个标准的Servlet ,它不能处理用户请求,也不能对客户端生成响应。主要用于对HttpServletRequest 进行预处理,也可以对HttpServletResponse 进行后处理。
Web 应用程序可以根据特定的目的定义若干个不同的过滤器,由此形成过滤器链。过滤链的好处是,执行过程中任何时候都可以打断,只要不执行chain.doFilter()就不会再执行后面的过滤器和请求的内容。而在实际使用时,就要特别注意过滤链的执行顺序问题
1.2DelegatingFilterProxy
Spring框架提供了一个叫做 DelegatingFilterProxy的类,它是SpringSecurity框架当中的Filter接口的实现类。通过DelegatingFilterProxy可以使Servlet容器和Spring框架的ApplicationContext对象之间建立起“桥梁”。
解释上一句:Servlet容器允许使用其自身的标准注册Servlet当中的Filter,但是它并不能直接注册SpringSecurity当中的Fillter。所以,Servlet容器通过注册DelegatingFilterProxy对象,使用DelegatingFilterProxy来代理执行SpringSecurity当中的所有Filter。
通过上述的方式,SpringSecurity成功的“嵌入”到了Servlet的过滤器链当中。
1.3FilterChainProxy
由上述可知,Servlet容器是使用DelegatingFilterProxy来代理执行SpringSecurity当中的Filter。事实上却并不是如此,而是由DelegatingFilterProxy创建的FilterChainProxy对象来真正代理SpringSecurity当中实例化过的Filter。既然真正的代理对象已经找到了,那么被代理的Filter们存储在哪里呢?
上源码:
public class DelegatingFilterProxy extends GenericFilterBean {
@Nullable
private String contextAttribute;
@Nullable
private WebApplicationContext webApplicationContext;
@Nullable
private String targetBeanName;
private boolean targetFilterLifecycle;
@Nullable
private volatile Filter delegate;//代理的Filter对象
private final Object delegateMonitor;
//初始化SpringSecurity当中的Filter
protected void initFilterBean() throws ServletException {
synchronized(this.delegateMonitor) {
if (this.delegate == null) {
if (this.targetBeanName == null) {
this.targetBeanName = this.getFilterName();
}
WebApplicationContext wac = this.findWebApplicationContext();
if (wac != null) {
//调用initDelegate方法获取当前代理的filter
this.delegate = this.initDelegate(wac);
}
}
}
}
//doFilter()方法,与Servlet当中Filter的方法是否十分相似呢?
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
Filter delegateToUse = this.delegate;
if (delegateToUse == null) {
synchronized(this.delegateMonitor) {
delegateToUse = this.delegate;
if (delegateToUse == null) {
WebApplicationContext wac = this.findWebApplicationContext();
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener or DispatcherServlet registered?");
}
//调用initDelegate方法获取当前代理的filter
delegateToUse = this.initDelegate(wac);
}
this.delegate = delegateToUse;
}
}
this.invokeDelegate(delegateToUse, request, response, filterChain);
}
protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
//通过debug发现此处的targetBeanName为springSecurityFilterChain
String targetBeanName = this.getTargetBeanName();
Assert.state(targetBeanName != null, "No target bean name set");
//通过debug我们发现delegate当中封装着SpringSecurity的多个Filter
Filter delegate = (Filter)wac.getBean(targetBeanName, Filter.class);
if (this.isTargetFilterLifecycle()) {
delegate.init(this.getFilterConfig());
}
return delegate;
}
//调用delegate当中封装着的Filter对象的doFilter方法
protected void invokeDelegate(Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
delegate.doFilter(request, response, filterChain);
}
}
进入FilterChainProxy内部:
public class FilterChainProxy extends GenericFilterBean {
private static final Log logger = LogFactory.getLog(FilterChainProxy.class);
private static final String FILTER_APPLIED = FilterChainProxy.class.getName().concat(".APPLIED");
private List<SecurityFilterChain> filterChains;//通过分析,发现SpringSecurity当中的Filter都封装在SecurityFilterChain当中
private FilterChainProxy.FilterChainValidator filterChainValidator;
private HttpFirewall firewall;
private RequestRejectedHandler requestRejectedHandler;
//过滤的方法
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
if (!clearContext) {
//调用doFilterInternal方法
this.doFilterInternal(request, response, chain);
} else {
try {
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
this.doFilterInternal(request, response, chain);
} catch (RequestRejectedException var9) {
this.requestRejectedHandler.handle((HttpServletRequest)request, (HttpServletResponse)response, var9);
} finally {
SecurityContextHolder.clearContext();
request.removeAttribute(FILTER_APPLIED);
}
}
}
private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
FirewalledRequest firewallRequest = this.firewall.getFirewalledRequest((HttpServletRequest)request);
HttpServletResponse firewallResponse = this.firewall.getFirewalledResponse((HttpServletResponse)response);
//注意:此处是一个集合,里面封装着Filter类型的对象,很可疑!!!
//继续查看getFilters()方法
List<Filter> filters = this.getFilters((HttpServletRequest)firewallRequest);
if (filters != null && filters.size() != 0) {
if (logger.isDebugEnabled()) {
logger.debug(LogMessage.of(() -> {
return "Securing " + requestLine(firewallRequest);
}));
}
FilterChainProxy.VirtualFilterChain virtualFilterChain = new FilterChainProxy.VirtualFilterChain(firewallRequest, chain, filters);
virtualFilterChain.doFilter(firewallRequest, firewallResponse);
} else {
if (logger.isTraceEnabled()) {
logger.trace(LogMessage.of(() -> {
return "No security for " + requestLine(firewallRequest);
}));
}
firewallRequest.reset();
chain.doFilter(firewallRequest, firewallResponse);
}
}
private List<Filter> getFilters(HttpServletRequest request) {
int count = 0;
//发现Filter原来都封装在filterChains里面,刚好对应上图delegate对象当中的filterChain属性
Iterator var3 = this.filterChains.iterator();
SecurityFilterChain chain;
do {
if (!var3.hasNext()) {
return null;
}
chain = (SecurityFilterChain)var3.next();
if (logger.isTraceEnabled()) {
++count;
logger.trace(LogMessage.format("Trying to match request against %s (%d/%d)", chain, count, this.filterChains.size()));
}
} while(!chain.matches(request));
return chain.getFilters();
}
}
进入SecurityFilterChain的内部:
public final class DefaultSecurityFilterChain implements SecurityFilterChain {
private static final Log logger = LogFactory.getLog(DefaultSecurityFilterChain.class);
private final RequestMatcher requestMatcher;
private final List<Filter> filters;//该集合当中封装着SpringSecurity的Filter
public DefaultSecurityFilterChain(RequestMatcher requestMatcher, Filter... filters) {
this(requestMatcher, Arrays.asList(filters));
}
public DefaultSecurityFilterChain(RequestMatcher requestMatcher, List<Filter> filters) {
logger.info(LogMessage.format("Will secure %s with %s", requestMatcher, filters));
this.requestMatcher = requestMatcher;
this.filters = new ArrayList(filters);
}
public RequestMatcher getRequestMatcher() {
return this.requestMatcher;
}
public List<Filter> getFilters() {
return this.filters;
}
public boolean matches(HttpServletRequest request) {
return this.requestMatcher.matches(request);
}
public String toString() {
return this.getClass().getSimpleName() + " [RequestMatcher=" + this.requestMatcher + ", Filters=" + this.filters + "]";
}
}
通过一步步的分析,我们最终发现SpringSecurity的Filter们都被封装在一个SecurityFilterChain类型的对象当中。
1.4SecurityFilterChain
FilterChainProxy使用SecurityFilterChain对象去决定当前哪一个SpringSecurity的Filter应该被调用。
注意:SecurityFilterChain当中的Security Filters一般都是注册到IOC当中的bean对象,但是其并不是通过SecurityFilterChain进行注册的,而是通过DelegatingFilterProxy(上图的FilterChainProxy类型的delegate对象在初始化时,其内部已经加载过Security Filters了)。
1.5Security Filters
Security Filters通过 SecurityFilterChain API被注入到了 FilterChainProxy。通常你并不需要清楚SpringSecurity当中的filters的执行顺序,然而有时知道他们的执行顺序也许会对你有用。
下面列出一些SpringSecurity当中的Filter的执行顺序。
- ChannelProcessingFilter
- WebAsyncManagerIntegrationFilter
- SecurityContextPersistenceFilter
- HeaderWriterFilter
- CorsFilter
- CsrfFilter
- LogoutFilter
- OAuth2AuthorizationRequestRedirectFilter
- Saml2WebSsoAuthenticationRequestFilter
- X509AuthenticationFilter
- AbstractPreAuthenticatedProcessingFilter
- CasAuthenticationFilter
- OAuth2LoginAuthenticationFilter
- Saml2WebSsoAuthenticationFilter
- UsernamePasswordAuthenticationFilter
- OpenIDAuthenticationFilter
- DefaultLoginPageGeneratingFilter
- DefaultLogoutPageGeneratingFilter
- ConcurrentSessionFilter
- DigestAuthenticationFilter
- BearerTokenAuthenticationFilter
- BasicAuthenticationFilter
- RequestCacheAwareFilter
- SecurityContextHolderAwareRequestFilter
- JaasApiIntegrationFilter
- RememberMeAuthenticationFilter
- AnonymousAuthenticationFilter
- OAuth2AuthorizationCodeGrantFilter
- SessionManagementFilter
- ExceptionTranslationFilter
- FilterSecurityInterceptor
- SwitchUserFilter
1.6Handling Security Exceptions
ExceptionTranslationFilter被插入到 FilterChainProxy当中作为其中之一的Filter。该Filter能够将 AccessDeniedException 和 AuthenticationException类型的异常添加到http响应当中。
- 首先,当FilterChainProxy执行到SecurityFilterChain当中的ExceptionTranslationFilter时,ExceptionTranslationFilter对象会调用FilterChain.doFilter(request, response)方法去继续执行当前应用剩下的Filter。
- 如果当前用户在下一个过滤器FilterSecurityInterceptor当中进行授权时,发现其没有经过认证或者抛出AuthenticationException时,就会开启认证。
·首先 SecurityContextHolder会被清楚。
·HttpServletRequest会被保存在 RequestCache当中。当用户认证成功过后,可以从 RequestCache当中获取原先的request。
· AuthenticationEntryPoint用于向客户端发送认证信息。例如:它可能发送一个重定向到登录页面的请求或者发送一个WWW-Authenticate请求头。 - 否则,如果返回的异常类型是AccessDeniedException,ExceptionTranslationFilter将会调用this.handleSpringSecurityException(request, response, chain, (RuntimeException)securityException)去处理响应的异常。
try {
filterChain.doFilter(request, response);
} catch (AccessDeniedException | AuthenticationException ex) {
if (!authenticated || ex instanceof AuthenticationException) {
startAuthentication();
} else {
accessDenied();
}
}
注意:如果 SecurityFilterChain当中前面的Filter抛出AuthenticationException 或者 AccessDeniedException类型的异常,该异常也会在这里被捕获并处理。
二、认证流程分析
2.1登录流程
让我们看一下在SpringSecurity中基于表单的工作流程是怎样的。首先,我们来了解一下用户未登录时如何重定向到登录表单界面。如下图所示:
1.首先,一个用户向未授权的资源发送未经过认证的请求。
2.SpringSecurity的FilterSecurityInterceptor对象拦截到用户发送的请求,并开启认证流程。
3.因为该请求没有经过认证,所以必然会抛出一个AccessDeniedException类型的异常(认证细节稍后稍后再讲)。
4.ExceptionTranslationFilter这个过滤器类获取到该异常类之后,将会进行判断。如果发现是未认证类型的异常,将会使用配置过 AuthenticationEntryPoint对象向客户端发送一个重定向到/login页面的响应。
5.浏览器将会向重定向页面再次发送请求
6.通过loginController进行登录验证
接下来,我们再来详细刨析一下使用表单进行登录的认证流程。
2.2UsernamePasswordAuthenticationFilter
当用户名和密码通过表单进行提交之后,将由UsernamePasswordAuthenticationFilter进行认证用户的登录信息。这个UsernamePasswordAuthenticationFilter类型的过滤器继承自 AbstractAuthenticationProcessingFilter,因此UsernamePasswordAuthenticationFilter使用的是父类的doFilter()方法进行拦截验证。
AbstractAuthenticationProcessingFilter类源码分析:
我们发现AbstractAuthenticationProcessingFilter类当中的attemptAuthentication()方法并没有立即实现,而是通过它的子类UsernamePasswordAuthenticationFilter进行实现。
2.3AuthenticationManager
由上面源码得知,真正认证操作在AuthenticationManager里面!通过debug发现真正的认证的操作是在AuthenticationManager的实现类ProviderManager当中进行的:
public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {
private static final Log logger = LogFactory.getLog(ProviderManager.class);
private AuthenticationEventPublisher eventPublisher;
private List<AuthenticationProvider> providers;//封装不同的认证策略用于不同的登录方式
protected MessageSourceAccessor messages;
private AuthenticationManager parent;
private boolean eraseCredentialsAfterAuthentication;
public ProviderManager(List<AuthenticationProvider> providers) {
this(providers, (AuthenticationManager)null);
}
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 {
//调用provider的认证方法
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;
}
}
}
}
}
2.4AbstractUserDetailsAuthenticationProvider
咱们继续再找到AuthenticationProvider的实现类AbstractUserDetailsAuthenticationProvider:
public abstract class AbstractUserDetailsAuthenticationProvider implements AuthenticationProvider, InitializingBean, MessageSourceAware {
//去除无关代码
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, () -> {
return this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported");
});
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);
}
public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";
private PasswordEncoder passwordEncoder;
private volatile String userNotFoundEncodedPassword;
private UserDetailsService userDetailsService;
private UserDetailsPasswordService userDetailsPasswordService;
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
this.prepareTimingAttackProtection();
try {
//重点来了!主要就在这里了!
//UserDetails就是SpringSecurity自己的用户对象。
//this.getUserDetailsService()其实就是得到UserDetailsService的一个实现类
//loadUserByUsername里面就是真正的认证逻辑
//也就是说我们可以直接编写一个UserDetailsService的实现类,告诉SpringSecurity就可以了!
//loadUserByUsername方法中只需要返回一个UserDetails对象即可
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
//若返回null,就抛出异常,认证失败。
if (loadedUser == null) {
throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
} else {
//若有得到了UserDetails对象,返回即可。
return loadedUser;
}
} catch (UsernameNotFoundException var4) {
this.mitigateAgainstTimingAttack(authentication);
throw var4;
} catch (InternalAuthenticationServiceException var5) {
throw var5;
} catch (Exception var6) {
throw new InternalAuthenticationServiceException(var6.getMessage(), var6);
}
}
}
2.5UserDetailsService
UserDetailsService被DaoAuthenticationProvider通过用户登录时提交的用户名和密码用于获取自定义的用户名,密码,以及其他一下用于认证的属性。SpringSecurity提供了基于用于获取内存存储或者数据库存储自定义信息的UserDetailsService实现类。
具体的自定义的过程将在下方介绍。
2.6AbstractUserDetailsAuthenticationProvider中authenticate返回值
按理说到此已经知道自定义认证方法的怎么写了,但咱们把返回的流程也大概走一遍,上面不是说到返回了一个 UserDetails对象吗?跟着它,就又回到了AbstractUserDetailsAuthenticationProvider对象中authenticate方法的最后一行了。
public abstract class AbstractUserDetailsAuthenticationProvider implements AuthenticationProvider, InitializingBean, MessageSourceAware {
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
//最后一行返回值,调用了createSuccessAuthentication方法,此方法就在下面!
return this.createSuccessAuthentication(principalToReturn, authentication, user);
}
//这里怎么又封装了一次UsernamePasswordAuthenticationToken,开局不是已经封装过了吗?
protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {
//那就从构造方法点进去看看,这才干啥了。
UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal, authentication.getCredentials(), this.authoritiesMapper.mapAuthorities(user.getAuthorities()));
result.setDetails(authentication.getDetails());
return result;
}
}
2.7UsernamePasswordAuthenticationToken
来到UsernamePasswordAuthenticationToken对象发现里面有两个构造方法
public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = 510L;
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);
}
2.8AbstractAuthenticationToken
再点进去super(authorities)看看:
public abstract class AbstractAuthenticationToken implements Authentication, CredentialsContainer {
private final Collection<GrantedAuthority> authorities;
private Object details;
private boolean authenticated = false;
public AbstractAuthenticationToken(Collection<? extends GrantedAuthority> authorities) {
//这时两个参数那个分支
if (authorities == null) {
this.authorities = AuthorityUtils.NO_AUTHORITIES;
} else {
//三个参数的,看这里!
Iterator var2 = authorities.iterator();
//原来是多个了添加权限信息的步骤
GrantedAuthority a;
do {
if (!var2.hasNext()) {
ArrayList<GrantedAuthority> temp = new ArrayList(authorities.size());
temp.addAll(authorities);
this.authorities = Collections.unmodifiableList(temp);
return;
}
a = (GrantedAuthority)var2.next();
} while(a != null);
//若没有权限信息,是会抛出异常的!
throw new IllegalArgumentException("Authorities collection cannot contain any null elements");
}
}
}
由此,咱们需要牢记自定义认证业务逻辑返回的UserDetails对象中一定要放置权限信息啊!
2.9AbstractAuthenticationProcessingFilter
回到最初的起点,UsernamePasswordAuthenticationFilter使用的是父类的doFilter()方法进行的认证操作。点开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 (!this.requiresAuthentication(request, response)) {
chain.doFilter(request, response);
} else {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Request is to process authentication");
}
Authentication authResult;
try {
//此处使用的是子类UsernamePasswordAuthenticationFilter的方法
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);
}
}
//成功走successfulAuthentication
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Authentication success. Updating SecurityContextHolder to contain: " + authResult);
}
//认证成功,将认证信息存储到SecurityContext中!
SecurityContextHolder.getContext().setAuthentication(authResult);
//登录成功调用rememberMeServices
this.rememberMeServices.loginSuccess(request, response, authResult);
if (this.eventPublisher != null) {
this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
}
this.successHandler.onAuthenticationSuccess(request, response, authResult);
}
//失败走unsuccessfulAuthentication
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
SecurityContextHolder.clearContext();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Authentication request failed: " + failed.toString(), failed);
this.logger.debug("Updated SecurityContextHolder to contain null Authentication");
this.logger.debug("Delegating to authentication failure handler " + this.failureHandler);
}
this.rememberMeServices.loginFail(request, response);
this.failureHandler.onAuthenticationFailure(request, response, failed);
}
可见AbstractAuthenticationProcessingFilter这个过滤器对于认证成功与否,做了两个分支,成功执行successfulAuthentication,失败执行unsuccessfulAuthentication。
在successfulAuthentication内部,将认证信息存储到了SecurityContext中。并调用了loginSuccess方法,这就是常见的“记住我”功能!
三、自定义实现认证功能
3.1定义自己的UserService接口继承UserDetailsService
public interface UserService extends UserDetailsService {
}
public UserServiceImpl implements UserService{
//编写loadUserByUsername业务
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//调用持久层接口查询数据库中的用户信息
SysUser sysUser = userDao.findByName(username);
if(sysUser==null){
//若用户名不对,直接返回null,表示认证失败。
return null;
}
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
//获取用户的权限
List<SysRole> roles = sysUser.getRoles();
for (SysRole role : roles) {
authorities.add(new SimpleGrantedAuthority(role.getRoleName()));
}
//最终需要返回一个SpringSecurity的UserDetails对象,{noop}表示不加密认证。
return new User(sysUser.getUsername(), "{noop}"+sysUser.getPassword(), authorities);
}
}
3.2在SpringSecurity主配置文件中指定认证使用的业务对象
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true,securedEnabled =true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public UserDetailsService userDetailsService() {
return new UserServiceImpl();
}
}
四、加密认证
SpringSecurity支持使用PasswordEncoder来安全的存储用户密码。可以通过定义一个PasswordEncoder类型的实现类并注入到容器当中,SpringSecurity将会获取该bean对象用于密码加密。
前提条件:自定义的用户信息当中存储的用户密码必须是使用相应的PasswordEncoder进行加密后的密码。
4.1在IOC容器中提供加密对象
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true,securedEnabled =true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
源码分析:
public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";
private PasswordEncoder passwordEncoder;
private volatile String userNotFoundEncodedPassword;
private UserDetailsService userDetailsService;
private UserDetailsPasswordService userDetailsPasswordService;
public DaoAuthenticationProvider() {
this.setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
}
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 {
//此处,获取通过retrieveUser方法得到的自定义用户信息中的密码
String presentedPassword = authentication.getCredentials().toString();
//通过passwordEncoder判断用户登录时的密码与自定义用户信息中的密码是否一致
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"));
}
}
}
}
4.2修改认证方法
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
SysUser sysUser = userDao.findByName(username);
if(sysUser==null){
//若用户名不对,直接返回null,表示认证失败。
return null;
}
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
List<SysRole> roles = sysUser.getRoles();
for (SysRole role : roles) {
authorities.add(new SimpleGrantedAuthority(role.getRoleName()));
}
//最终需要返回一个SpringSecurity的UserDetails对象,{noop}表示不加密认证。
return new User(sysUser.getUsername(), sysUser.getPassword(), authorities);
}
4.3修改添加用户的操作
@Service
@Transactional
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Autowired
private BCryptPasswordEncoder passwordEncoder;
@Override
public void save(SysUser user) {
//对密码进行加密,然后再入库
user.setPassword(passwordEncoder.encode(user.getPassword()));
userDao.save(user);
}
//……
}
总结
Springecurity通过DelegatingFilterProxy使Security Filters最终与Servlet当中的Filters一同作用,用于完成用户的认证与授权操作。在这个过程中使用了代理模式和责任链模式等设计模式,在这里就不再做详细论述。本文旨在帮助初学者快速掌握SpringSecurity框架的主要对象和登录认证流程。文中不足之处,请多指点!!!!!!