Spring Security
1. 介绍
为基于 Spring 的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在 Spring 应用上下文中配置的 Bean,充分利用了 Spring IoC(Inversion of Control 控制反转),DI(Dependency Injection 依赖注入)和 AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。 --百度百科
功能
- 身份认证和授权
- 支持Servlet API集成
- 支持Spring Web MVC集成
- 其他
2. 核心组件
2.1 核心接口
Authentication
interface Authentication extends Principal, Serializable
// 权限信息,比如角色或者权限字符串
Collection<? extends GrantedAuthority> getAuthorities();
// 凭证信息,比如键入的密码
Object getCredentials();
// 额外信息,比如请求附带的IP
Object getDetails();
// 认证主体,比如实现DetailServices
Object getPrincipal();
// 是否认证标识位
boolean isAuthenticated();
void setAuthenticated(boolean isAuthenticated)
abstract class AbstractAuthenticationToken implements Authentication,
CredentialsContainer
private final Collection<GrantedAuthority> authorities;
private Object details;
private boolean authenticated = false;
class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken
private final Object principal;
private Object credentials;
AuthenticationManager
interface AuthenticationManager
Authentication authenticate(Authentication authentication)
class ProviderManager implements AuthenticationManager, MessageSourceAware,
InitializingBean
private List<AuthenticationProvider> providers = Collections.emptyList();
private AuthenticationManager parent;
// 其他
// 遍历providers执行authenticate
Authentication authenticate(Authentication authentication) {}
AuthenticationProvider
abstract class AbstractUserDetailsAuthenticationProvider implements
AuthenticationProvider, InitializingBean, MessageSourceAware
Authentication authenticate(Authentication authentication) {
// 1. 获取username
// 2. 查缓存
// 3. retrieveUser: 模板方法,子类DaoAuthenticationProvider执行UserDetailsService loadUserByUsername获取UserDetails
// 4. pre check:isAccountNonLocked、isEnabled、isAccountNonExpired
// 5. 密码检查: additionalAuthenticationChecks
// 6. post check: isCredentialsNonExpired
// 7. createSuccessAuthentication: 用户名、密码、权限等填充,返回
}
class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider
UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) {
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
// null,抛异常,否则返回
}
UserDetails和UserDetailsService
关键是获取权限列表
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;
SecurityContext和SecurityContextHolder
持有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;
2.2 过滤器链
过滤器链
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
]
过滤器:UsernamePasswordAuthenticationFilter
抽象父类:AbstractAuthenticationProcessingFilter
匹配到的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();
子类:UsernamePasswordAuthenticationFilter
默认处理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);
过滤器:FilterSecurityInterceptor
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);
}
}
3. 流程
关键:
将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);
4. 入口
入口:@EnableWebSecurity
@Import({ WebSecurityConfiguration.class,
SpringWebMvcImportSelector.class,
OAuth2ImportSelector.class })
@EnableGlobalAuthentication
@Import(AuthenticationConfiguration.class)
WebSecurityConfiguration
AuthenticationConfiguration
生成bean AuthenticationManager authenticationManager
入口:继承WebSecurityConfigurerAdapter
JWT
在3的基础之上:
jwt过滤器:在UsernamePasswordAuthenticationFilter之前过滤
读取token,拿到登录信息,查询缓存,得到认证用户信息,填充到SecurityContextHolder
token刷新
参考
(推荐)SpringSecurity+JWT认证流程解析https://juejin.im/post/6846687598442708999
芋道源码http://www.iocoder.cn/Spring-Security/good-collection/?vip