为基于 Spring 的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在 Spring 应用上下文中配置的 Bean,充分利用了 Spring IoC(Inversion of Control 控制反转),DI(Dependency Injection 依赖注入)和 AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。 --百度百科
关键是获取权限列表
interface UserDetails extends Serializable Collection<? extends GrantedAuthority> getAuthorities(); String getUsername(); String getPassword(); boolean isAccountNonExpired(); boolean isAccountNonLocked(); boolean isCredentialsNonExpired(); boolean isEnabled(); interface UserDetailsService UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;持有Authentication
class SecurityContextHolder // 默认实现: ThreadLocalSecurityContextHolderStrategy private static SecurityContextHolderStrategy strategy; class ThreadLocalSecurityContextHolderStrategy implements SecurityContextHolderStrategy private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<>(); class SecurityContextImpl implements SecurityContext private Authentication authentication;过滤器链 14:48:21.041 [main] INFO o.s.s.w.DefaultSecurityFilterChain - [,43] - Creating filter chain: any request, [ org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@9679750, org.springframework.security.web.context.SecurityContextPersistenceFilter@77865933, org.springframework.security.web.header.HeaderWriterFilter@18578491, org.springframework.security.web.csrf.CsrfFilter@2373ad99, org.springframework.security.web.authentication.logout.LogoutFilter@f88bfbe, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@5ed4bc, org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@4364712f, org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@9b9a327, org.springframework.security.web.authentication.www.BasicAuthenticationFilter@7f1ef916, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@4d18b73a, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@53c6f96d, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@75a0c890, org.springframework.security.web.session.SessionManagementFilter@671c4166, org.springframework.security.web.access.ExceptionTranslationFilter@67064bdc, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@d613308 ]
匹配到的url,执行认证
private RequestMatcher requiresAuthenticationRequestMatcher;例如:
public UsernamePasswordAuthenticationFilter() { super(new AntPathRequestMatcher("/login", "POST")); } this.requiresAuthenticationRequestMatcher = requiresAuthenticationRequestMatcher;认证
private AuthenticationManager authenticationManager;成功和失败的处理handler,可实现接口自定义处理流程
private AuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler(); private AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();默认处理post /login。
可extends UsernamePasswordAuthenticationFilter,自定义认证url和登录处理(比如额外的验证码等信息需要处理)
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
// 1. 请求URL是否为POST: request.getMethod().equals("POST") // 2. 获取用户名和密码: request.getParameter // 3. 获取需要认证的实体: UsernamePasswordAuthenticationToken UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken( username, password); // 4. 填充detail:比如remoteAddress和sessionId setDetails(request, authRequest); // 5. AuthenticationManager执行authenticate,并返回Authentication:填充权限、(DB)用户信息等 return this.getAuthenticationManager().authenticate(authRequest);doFilter --> invoke
--> super.beforeInvocation(fi);
--> super.afterInvocation(token, null);
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { FilterInvocation fi = new FilterInvocation(request, response, chain); invoke(fi); } public void invoke(FilterInvocation fi) throws IOException, ServletException { if ((fi.getRequest() != null) && (fi.getRequest().getAttribute(FILTER_APPLIED) != null) && observeOncePerRequest) { fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); } else { // first time this request being called, so perform security checking if (fi.getRequest() != null && observeOncePerRequest) { fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE); } InterceptorStatusToken token = super.beforeInvocation(fi); try { fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); } finally { super.finallyInvocation(token); } super.afterInvocation(token, null); } }关键:
将request请求封装到UsernamePasswordAuthenticationToken
调用authenticationManager.authenticate(authencationToken),得到Authentication authentication
保存到Spring Security:SecurityContextHolder.getContext().setAuthentication(authentication);
缓存存储并返回
输入:用户密码
处理filter: UsernamePasswordAuthenticationFilter
从request获取输入,组装成UsernamePasswordAuthenticationToken
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken( username, password);认证,并返回填充信息的Authentication authResult对象
return this.getAuthenticationManager().authenticate(authRequest);class ProviderManager implements AuthenticationManager
List<AuthenticationProvider> providers result = provider.authenticate(authentication);abstract class AbstractUserDetailsAuthenticationProvider implements AuthenticationProvider
class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider
UserDetails user = this.userCache.getUserFromCache(username); if (user == null) { cacheWasUsed = false; user = retrieveUser(username, authentication); } preAuthenticationChecks.check(user); additionalAuthenticationChecks(user, authentication); postAuthenticationChecks.check(user); if (!cacheWasUsed) { this.userCache.putUserInCache(user); } return createSuccessAuthentication(principalToReturn, authentication, user); protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) { UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username); if (loadedUser == null) { throw new InternalAuthenticationServiceException(".."); } return loadedUser; }成功or失败:
try { authResult = attemptAuthentication(request, response); if (authResult == null) { return; } } catch (InternalAuthenticationServiceException failed) { logger.error(".."); unsuccessfulAuthentication(request, response, failed); return; } catch (AuthenticationException failed) { unsuccessfulAuthentication(request, response, failed); return; } // Authentication success if (continueChainBeforeSuccessfulAuthentication) { chain.doFilter(request, response); } successfulAuthentication(request, response, chain, authResult);success: successHandler.onAuthenticationSuccess(request, response, authResult);
failure: failureHandler.onAuthenticationFailure(request, response, failed);
入口:@EnableWebSecurity @Import({ WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, OAuth2ImportSelector.class }) @EnableGlobalAuthentication @Import(AuthenticationConfiguration.class)
WebSecurityConfiguration AuthenticationConfiguration 生成bean AuthenticationManager authenticationManager入口:继承WebSecurityConfigurerAdapter
在3的基础之上:
jwt过滤器:在UsernamePasswordAuthenticationFilter之前过滤
读取token,拿到登录信息,查询缓存,得到认证用户信息,填充到SecurityContextHolder
token刷新
(推荐)SpringSecurity+JWT认证流程解析https://juejin.im/post/6846687598442708999
芋道源码http://www.iocoder.cn/Spring-Security/good-collection/?vip