想要深刻spring security的authentication (身份验证)和access-control(访问权限控制)工做流程,必须清楚spring security的主要技术点包括关键接口、类以及抽象类如何协同工做进行authentication 和access-control的实现。web
常见认证和受权流程能够分红:算法
上述前三点为spring security认证验证环节:spring
根据上述描述的过程,咱们接下来主要去分析其中涉及的一下Component、Service、Filter。数据库
SecurityContextHolder提供对SecurityContext的访问,存储security context(用户信息、角色权限等),并且其具备下列储存策略即工做模式:安全
SecurityContextHolder.MODE_THREADLOCAL(默认):使用ThreadLocal,信息可供此线程下的全部的方法使用,一种与线程绑定的策略,此自然很适合Servlet Web应用。cookie
SecurityContextHolder.MODE_GLOBAL:使用于独立应用session
SecurityContextHolder.MODE_INHERITABLETHREADLOCAL:具备相同安全标示的线程app
修改SecurityContextHolder的工做模式有两种方法 :ide
在默认ThreadLocal策略中,SecurityContextHolder为静态方法获取用户信息为:源码分析
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
String username = ((UserDetails)principal).getUsername();
} else {
String username = principal.toString();
}
复制代码
可是通常不须要自身去获取。 其中getAuthentication()返回一个Authentication认证主体,接下来分析Authentication、UserDetails细节。
Spring Security使用一个Authentication对象来描述当前用户的相关信息,其包含用户拥有的权限信息列表、用户细节信息(身份信息、认证信息)。Authentication为认证主体在spring security中时最高级别身份/认证的抽象,常见的实现类UsernamePasswordAuthenticationToken。Authentication接口源码:
public interface Authentication extends Principal, Serializable {
//权限信息列表,默认GrantedAuthority接口的一些实现类
Collection<? extends GrantedAuthority> getAuthorities();
//密码信息
Object getCredentials();
//细节信息,web应用中的实现接口一般为 WebAuthenticationDetails,它记录了访问者的ip地址和sessionId的值
Object getDetails();
//一般返回值为UserDetails实现类
Object getPrincipal();
boolean isAuthenticated();
void setAuthenticated(boolean var1) throws IllegalArgumentException;
}
复制代码
前面两个组件都涉及了UserDetails,以及GrantedAuthority其究竟是什么呢?2.3小节分析。
UserDetails提供从应用程序的DAO或其余安全数据源构建Authentication对象所需的信息,包含GrantedAuthority。其官方实现类为User,开发者能够实现其接口自定义UserDetails实现类。其接口源码:
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
复制代码
UserDetails与Authentication接口功能相似,其实含义便是Authentication为用户提交的认证凭证(帐号密码),UserDetails为系统中用户正确认证凭证,在UserDetailsService中的loadUserByUsername方法获取正确的认证凭证。 其中在getAuthorities()方法中获取到GrantedAuthority列表是表明用户访问应用程序权限范围,此类权限一般是“role(角色)”,例如ROLE_ADMINISTRATOR或ROLE_HR_SUPERVISOR。GrantedAuthority接口常见的实现类SimpleGrantedAuthority。
AuthenticationManager是认证相关的核心接口,是认证一切的起点。但常见的认证流程都是AuthenticationManager实现类ProviderManager处理,并且ProviderManager实现类基于委托者模式维护AuthenticationProvider 列表用于不一样的认证方式。例如:
AuthenticationProvider为
ProviderManager源码分析:
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
Authentication result = null;
//AuthenticationProvider列表依次认证
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) {
continue;
}
try {
//每一个AuthenticationProvider进行认证
result = provider.authenticate(authentication)
if (result != null) {
copyDetails(authentication, result);
break;
}
}
....
catch (AuthenticationException e) {
lastException = e;
}
}
//进行父类AuthenticationProvider进行认证
if (result == null && parent != null) {
// Allow the parent to try.
try {
result = parent.authenticate(authentication);
}
catch (AuthenticationException e) {
lastException = e;
}
}
// 若是有Authentication信息,则直接返回
if (result != null) {
if (eraseCredentialsAfterAuthentication
&& (result instanceof CredentialsContainer)) {
//清除密码
((CredentialsContainer) result).eraseCredentials();
}
//发布登陆成功事件
eventPublisher.publishAuthenticationSuccess(result);
return result;
}
//若是都没认证成功,抛出异常
if (lastException == null) {
lastException = new ProviderNotFoundException(messages.getMessage(
"ProviderManager.providerNotFound",
new Object[] { toTest.getName() },
"No AuthenticationProvider found for {0}"));
}
prepareException(lastException, authentication);
throw lastException;
}
复制代码
ProviderManager 中的AuthenticationProvider列表,会依照次序去认证,默认策略下,只须要经过一个AuthenticationProvider的认证,便可被认为是登陆成功,并且AuthenticationProvider认证成功后返回一个Authentication实体,并为了安全会进行清除密码。若是全部认证器都没法认证成功,则ProviderManager 会抛出一个ProviderNotFoundException异常。
UserDetailsService接口做用是从特定的地方获取认证的数据源(帐号、密码)。如何获取到系统中正确的认证凭证,经过loadUserByUsername(String username)获取认证信息,并且其只有一个方法:
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
复制代码
其常见的实现类从数据获取的JdbcDaoImpl实现类,从内存中获取的InMemoryUserDetailsManager实现类,不过咱们能够实现其接口自定义UserDetailsService实现类,以下:
public class CustomUserService implements UserDetailsService {
@Autowired
//用户mapper
private UserInfoMapper userInfoMapper;
@Autowired
//用户权限mapper
private PermissionInfoMapper permissionInfoMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserInfoDTO userInfo = userInfoMapper.getUserInfoByUserName(username);
if (userInfo != null) {
List<PermissionInfoDTO> permissionInfoDTOS = permissionInfoMapper.findByAdminUserId(userInfo.getId());
List<GrantedAuthority> grantedAuthorityList = new ArrayList<>();
//组装权限GrantedAuthority object
for (PermissionInfoDTO permissionInfoDTO : permissionInfoDTOS) {
if (permissionInfoDTO != null && permissionInfoDTO.getPermissionName() != null) {
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(
permissionInfoDTO.getPermissionName());
grantedAuthorityList.add(grantedAuthority);
}
}
//返回用户信息
return new User(userInfo.getUserName(), userInfo.getPasswaord(), grantedAuthorityList);
}else {
//抛出用户不存在异常
throw new UsernameNotFoundException("admin" + username + "do not exist");
}
}
}
复制代码
AccessDecisionManager是由AbstractSecurityInterceptor调用,负责作出最终的访问控制决策。
AccessDecisionManager接口源码:
//访问控制决策
void decide(Authentication authentication, Object secureObject,Collection<ConfigAttribute> attrs)
throws AccessDeniedException;
//是否支持处理传递的ConfigAttribute
boolean supports(ConfigAttribute attribute);
//确认class是否为AccessDecisionManager
boolean supports(Class clazz);
复制代码
SecurityMetadataSource包含着AbstractSecurityInterceptor访问受权所需的元数据(动态url、动态受权所需的数据),在AbstractSecurityInterceptor受权模块中结合AccessDecisionManager进行访问受权。其涉及了ConfigAttribute。 SecurityMetadataSource接口:
Collection<ConfigAttribute> getAttributes(Object object)
throws IllegalArgumentException;
Collection<ConfigAttribute> getAllConfigAttributes();
boolean supports(Class<?> clazz);
复制代码
咱们还能够自定义SecurityMetadataSource数据源,实现接口FilterInvocationSecurityMetadataSource。例:
public class MyFilterSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
public List<ConfigAttribute> getAttributes(Object object) {
FilterInvocation fi = (FilterInvocation) object;
String url = fi.getRequestUrl();
String httpMethod = fi.getRequest().getMethod();
List<ConfigAttribute> attributes = new ArrayList<ConfigAttribute>();
// Lookup your database (or other source) using this information and populate the
// list of attributes
return attributes;
}
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
public boolean supports(Class<?> clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
}
复制代码
为了存储安全,通常要对密码进行算法加密,而spring security提供了加密PasswordEncoder接口。其实现类有使用BCrypt hash算法实现的BCryptPasswordEncoder,SCrypt hashing 算法实现的SCryptPasswordEncoder实现类,实现类内部实现可看源码分析。而PasswordEncoder接口只有两个方法:
public interface PasswordEncoder {
//密码加密
String encode(CharSequence rawPassword);
//密码配对
boolean matches(CharSequence rawPassword, String encodedPassword);
}
复制代码
FilterSecurityInterceptor是Spring security受权模块入口,该类根据访问的用户的角色,权限受权访问那些资源(访问特定路径应该具有的权限)。
FilterSecurityInterceptor封装FilterInvocation对象进行操做,全部的请求到了这一个filter,若是这个filter以前没有执行过的话,那么首先执行其父类AbstractSecurityInterceptor提供的InterceptorStatusToken token = super.beforeInvocation(fi),在此方法中使用AuthenticationManager获取Authentication中用户详情,使用ConfigAttribute封装已定义好访问权限详情,并使用AccessDecisionManager.decide()方法进行访问权限控制。
FilterSecurityInterceptor源码分析:
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);
}
//回调其继承的抽象类AbstractSecurityInterceptor的方法
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
finally {
super.finallyInvocation(token);
}
super.afterInvocation(token, null);
}
}
复制代码
AbstractSecurityInterceptor源码分析:
protected InterceptorStatusToken beforeInvocation(Object object) {
....
//获取全部访问权限(url-role)属性列表(已定义在数据库或者其余地方)
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
.getAttributes(object);
....
//获取该用户访问信息(包括url,访问权限)
Authentication authenticated = authenticateIfRequired();
// Attempt authorization
try {
//进行受权访问
this.accessDecisionManager.decide(authenticated, object, attributes);
}catch
....
}
复制代码
UsernamePasswordAuthenticationFilter使用username和password表单登陆使用的过滤器,也是最为经常使用的过滤器。其源码:
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
//获取表单中的用户名和密码
String username = obtainUsername(request);
String password = obtainPassword(request);
...
username = username.trim();
//组装成username+password形式的token
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
//交给内部的AuthenticationManager去认证,并返回认证信息
return this.getAuthenticationManager().authenticate(authRequest);
}
复制代码
其主要代码为建立UsernamePasswordAuthenticationToken的Authentication实体以及调用AuthenticationManager进行authenticate认证,根据认证结果执行successfulAuthentication或者unsuccessfulAuthentication,不管成功失败,通常的实现都是转发或者重定向等处理,再也不细究AuthenticationSuccessHandler和AuthenticationFailureHandle。兴趣的能够研究一下其父类AbstractAuthenticationProcessingFilter过滤器。
AnonymousAuthenticationFilter是匿名登陆过滤器,它位于经常使用的身份认证过滤器(如UsernamePasswordAuthenticationFilter、BasicAuthenticationFilter、RememberMeAuthenticationFilter)以后,意味着只有在上述身份过滤器执行完毕后,SecurityContext依旧没有用户信息,AnonymousAuthenticationFilter该过滤器才会有意义——基于用户一个匿名身份。 AnonymousAuthenticationFilter源码分析:
public class AnonymousAuthenticationFilter extends GenericFilterBean implements
InitializingBean {
...
public AnonymousAuthenticationFilter(String key) {
this(key, "anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));
}
...
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
if (SecurityContextHolder.getContext().getAuthentication() == null) {
//建立匿名登陆Authentication的信息
SecurityContextHolder.getContext().setAuthentication(
createAuthentication((HttpServletRequest) req));
...
}
chain.doFilter(req, res);
}
//建立匿名登陆Authentication的信息方法
protected Authentication createAuthentication(HttpServletRequest request) {
AnonymousAuthenticationToken auth = new AnonymousAuthenticationToken(key,
principal, authorities);
auth.setDetails(authenticationDetailsSource.buildDetails(request));
return auth;
}
}
复制代码
SecurityContextPersistenceFilter的两个主要做用即是request来临时,建立SecurityContext安全上下文信息和request结束时清空SecurityContextHolder。源码后续分析。
. AbstractAuthenticationProcessingFilter:主要处理登陆
. FilterSecurityInterceptor:主要处理鉴权
通过上面对核心的Component、Service、Filter分析,初步了解了Spring Security工做原理以及认证和受权工做流程。Spring Security认证和受权还有不少负责的过程须要深刻了解,因此下次会对认证模块和受权模块进行更具体工做流程分析以及案例呈现。最后以上纯粹我的结合博客和官方文档总结,若有错请指出!