权限管理是一个几乎全部后台系统的都会涉及的一个重要组成部分,主要目的是对整个后台管理系统进行权限的控制。常见的基于角色的访问控制,其受权模型为“用户-角色-权限”,简明的说,一个用户拥有多个角色,一个角色拥有多个权限。其中,linux
实质是:
权限(Permission) = 资源(Resource) + 操做(Privilege)
角色(Role) = 权限的集合(a set of low-level permissions)
用户(User) = 角色的集合(high-level roles)数据库
权限管理过程:app
在实际项目中用户数量多,逐一的为每一个系统用户受权,这是极其繁琐的事,因此能够学习linux文件管理系统同样,设置group模式,一组有多个用户,能够为用户组受权相同的权限,简便多了。这样模式下:
每一个用户的全部权限=用户我的的权限+用户组所用的权限
用户组、用户、与角色三者关系以下:ide
再结合权限管理的页面权限、操做权限,如菜单的访问、功能模块的操做、按钮的操做等等,可把功能操做与资源统一管理,即让它们直接与权限关联起来,关系图以下: 源码分析
FilterSecurityInterceptor
doFilter()->invoke()
->AbstractSecurityInterceptor
beforeInvocation()
->SecurityMetadataSource 获取ConfigAttribute属性信息(从数据库或者其余数据源地方)
getAttributes()
->AccessDecisionManager() 基于AccessDecisionVoter实现受权访问
Decide()
->AccessDecisionVoter 受AccessDecisionManager委托实现受权访问
vote()
复制代码
默认受权过程会使用这样的工做流程,接下来来分析各个组件的功能与源码。post
FilterSecurityInterceptor为受权拦截器, 在FilterSecurityInterceptor中有一个封装了过滤链、request以及response的FilterInvocation对象进行操做,在FilterSecurityInterceptor,主要由invoke()调用其父类AbstractSecurityInterceptor的方法。学习
invoke()分析:ui
public void invoke(FilterInvocation fi) throws IOException, ServletException {
.....
// 获取accessDecisionManager权限决策后结果状态、以及权限属性
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
finally {
super.finallyInvocation(token);
}
super.afterInvocation(token, null);
}
}
复制代码
AbstractSecurityInterceptor 的受权过滤器主要方法beforeInvocation(),afterInvocation()以及authenticateIfRequired(),其最主要的方法beforeInvocation() 分析以下:this
protected InterceptorStatusToken beforeInvocation(Object object) {
....
//从SecurityMetadataSource的权限属性
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
.getAttributes(object);
if (attributes == null || attributes.isEmpty()) {
.....
publishEvent(new PublicInvocationEvent(object));
return null; // no further work post-invocation
}
//调用认证环节获取authenticated(包含用户的详细信息)
Authentication authenticated = authenticateIfRequired();
// Attempt authorization
try {
//进行关键的一步:受权的最终决策
this.accessDecisionManager.decide(authenticated, object, attributes);
}
catch (AccessDeniedException accessDeniedException) {
publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
accessDeniedException));
throw accessDeniedException;
}
// Attempt to run as a different user
Authentication runAs = this.runAsManager.buildRunAs(authenticated, object,
attributes);
if (runAs == null) {
if (debug) {
logger.debug("RunAsManager did not change Authentication object");
}
// no further work post-invocation
return new InterceptorStatusToken(SecurityContextHolder.getContext(), false,
attributes, object);
}
else {
if (debug) {
logger.debug("Switching to RunAs Authentication: " + runAs);
}
SecurityContext origCtx = SecurityContextHolder.getContext();
SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
SecurityContextHolder.getContext().setAuthentication(runAs);
// need to revert to token.Authenticated post-invocation
return new InterceptorStatusToken(origCtx, true, attributes, object);
}
}
复制代码
SecurityMetadataSource是从数据库或者其余数据源中加载ConfigAttribute,为了在AccessDecisionManager.decide() 最终决策中进行match。其有三个方法:url
Collection<ConfigAttribute> getAttributes(Object var1) throws IllegalArgumentException;//加载权限资源
Collection<ConfigAttribute> getAllConfigAttributes();//加载全部权限资源
boolean supports(Class<?> var1);
复制代码
AccessDecisionManager被AbstractSecurityInterceptor 拦截器调用进行最终访问控制决策。 并且由AuthenticationManager建立的Authentication object中的GrantedAuthority,首先被受权模块中的 AccessDecisionManager读取使用,当复杂的GrantedAuthority,getAuthority()为null,所以须要AccessDecisionManager专门支持GrantedAuthority实现以便了解其内容。
AccessDecisionManager接口方法:
void decide(Authentication authentication, Object secureObject, Collection<ConfigAttribute> attrs) throws AccessDeniedException;
boolean supports(ConfigAttribute attribute);
boolean supports(Class clazz);
复制代码
AccessDecisionManager.decide()将使用AccessDecisionVoter进行投票决策。AccessDecisionVoter进行投票访问控制决策,访问不经过就抛出AccessDeniedException。
** AccessDecisionVoter**接口方法:
int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attrs);
boolean supports(ConfigAttribute attribute);
boolean supports(Class clazz);
复制代码
AccessDecisionVoter的核心方法vote() 一般是获取Authentication的GrantedAuthority与已定义好的ConfigAttributes进行match,若是成功为投赞成票,匹配不成功为拒绝票,当ConfigAttributes中无属性时,才投弃票。
Spring Security提供了三种投票方式去实现AccessDecisionManager接口进行投票访问控制决策:
ConsensusBased: 大多数voter赞成访问就受权访问
AffirmativeBased: 只要一个以上voter赞成访问就受权访问,所有
UnanimousBased : 只有全体赞成了才受权访问
且AccessDecisionVoter用三个静态变量表示voter投票状况:
Note: 当全部voter都弃权时使用变量allowIfEqualGrantedDeniedDecisions来判断,true为经过,false抛出AccessDeniedException。
此外可自定义AccessDecisionManager实现接口,由于可能某些AccessDecisionVoter具备权重比高投票权或者某些AccessDecisionVoter具备一票否认权。AccessDecisionVoter的Spring security实现类RoleVoter和AuthenticatedVoter。RoleVoter为最为常见的AccessDecisionVoter,其为简单的权限表示,并之前缀为ROLE_,vote匹配规则也跟上面同样。
源码分析:
Public int vote(Authentication authentication,Object object,Collection<ConfigAttribute>attributes){
//用户传递的authentication为null,拒绝访问
if(authentication==null){
return ACCESS_DENIED;
}
int result=ACCESS_ABSTAIN;
Collection<?extendsGrantedAuthority>authorities=extractAuthorities(authentication);
//依次进行投票
for(ConfigAttributeattribute:attributes){
if(this.supports(attribute)){
result=ACCESS_DENIED;
//Attempt to find a matching granted authority
for(GrantedAuthorityauthority:authorities){
if(attribute.getAttribute().equals(authority.getAuthority())){
returnACCESS_GRANTED;
}
}
}
}
复制代码
自定义组件:
自定义FilterSecurityInterceptor,可仿写FilterSecurityInterceptor,实现抽象类AbstractSecurityInterceptor以及Filter接口,其主要的是把自定义的SecurityMetadataSource与自定义accessDecisionManager配置到自定义FilterSecurityInterceptor的拦截器中
自定义SecurityMetadataSource,实现接口FilterInvocationSecurityMetadataSource,实现从数据库或者其余数据源中加载ConfigAttribute(便是从数据库或者其余数据源中加载资源权限)
自定义accessDecisionManager,可以使用基于AccessDecisionVoter实现权限认证的官方UnanimousBased
自定义AccessDecisionVoter
自定义MyFilterSecurityInterceptor主要工做为:
加载自定义的SecurityMetadataSource到自定义的FilterSecurityInterceptor中;
加载自定义的AccessDecisionManager到自定义的FilterSecurityInterceptor中;
重写invoke方法
@Component
public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
private FilterInvocationSecurityMetadataSource securityMetadataSource;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain);
invoke(fi);
}
private void invoke(FilterInvocation fi) throws IOException, ServletException {
//fi里面有一个被拦截的url
//里面调用MyInvocationSecurityMetadataSource的getAttributes(Object object)这个方法获取fi对应的全部权限
//再调用MyAccessDecisionManager的decide方法来校验用户的权限是否足够
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
//执行下一个拦截器
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} finally {
super.afterInvocation(token, null);
}
}
@Override
public void destroy() {
}
@Override
public Class<?> getSecureObjectClass() {
return null;
}
@Override
public SecurityMetadataSource obtainSecurityMetadataSource() {
return this.securityMetadataSource;
}
public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {
return this.securityMetadataSource;
}
//设置自定义的FilterInvocationSecurityMetadataSource
@Autowired
public void setSecurityMetadataSource(MyFilterInvocationSecurityMetadataSource messageSource) {
this.securityMetadataSource = messageSource;
}
//设置自定义的AccessDecisionManager
@Override
@Autowired
public void setAccessDecisionManager(AccessDecisionManager accessDecisionManager) {
super.setAccessDecisionManager(accessDecisionManager);
}
}
复制代码
自定义MyFilterInvocationSecurityMetadataSource主要工做为:
从数据源中加载ConfigAttribute到SecurityMetadataSource资源器中
重写getAttributes()加载ConfigAttribute为AccessDecisionManager.decide()受权决策作准备。
@Component
public class MyFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
private Map<String, Collection<ConfigAttribute>> configAttubuteMap = null;
private void loadResourceDefine() {
//todo 加载数据库的全部权限
Collection<ConfigAttribute> attributes;
}
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
AntPathRequestMatcher matcher;
String resUrl;
HttpServletRequest request = ((FilterInvocation) object).getRequest();
//1.加载权限资源数据
if (configAttubuteMap == null) {
loadResourceDefine();
}
Iterator<String> iterator = configAttubuteMap.keySet().iterator();
while (iterator.hasNext()) {
resUrl = iterator.next();
matcher = new AntPathRequestMatcher(resUrl);
if (matcher.matches(request)) {
return configAttubuteMap.get(resUrl);
}
}
return null;
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
}
复制代码
自定义MyAccessDecisionManager主要工做为:
重写最终受权决策decide(),自定义受权访问策略
@Component
public class MyAccessDecisionManager implements AccessDecisionManager {
@Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
ConfigAttribute c;
String needRole;
if(null== configAttributes || configAttributes.size() <=0) {
return;
}
//1.获取已定义的好资源权限配置
Iterator<ConfigAttribute> iterable=configAttributes.iterator();
while (iterable.hasNext()){
c=iterable.next();
needRole=c.getAttribute();
//2.依次比对用户角色对应的资源权限
for (GrantedAuthority grantedAuthority:authentication.getAuthorities()){
if(needRole.trim().equals(grantedAuthority.getAuthority())){
return;
}
}
}
}
@Override
public boolean supports(ConfigAttribute attribute) {
return true;
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
复制代码
}
配置SecurityConfig主要工做为:
将FilterSecurityInterceptor拦截器加载WebSecurityConfig中
protected void configure(HttpSecurity http) throws Exception {
http.headers().frameOptions().disable().and()
//表单登陆
.formLogin()
.loginPage(SecurityConstants.APP_FORM_LOGIN_PAGE)
.loginProcessingUrl(SecurityConstants.APP_FORM_LOGIN_URL)
.successHandler(authenticationSuccessHandler())
.failureHandler(authenticationFailureHandler())
.and()
//应用sms认证配置
.apply(smsAuthenticationSecurityConfig)
.and()
//容许经过
.authorizeRequests()
.antMatchers(SecurityConstants.APP_MOBILE_VERIFY_CODE_URL,
SecurityConstants.APP_USER_REGISTER_URL,
SecurityConstants.APP_FORM_LOGIN_INDEX_URL)
.permitAll()//以上的请求都不须要认证
.and()
//“记住我”配置
.rememberMe()
.tokenRepository(jdbcTokenRepository())//token入库处理类
.tokenValiditySeconds(SecurityConstants.REMEMBER_ME_VERIFY_TIME)//remember-me有效时间设置
.rememberMeParameter(SecurityConstants.REMEMBER_ME_PARAM_NAME)//请求参数名设置
.and()
.csrf().disable();
//增长自定义权限受权拦截器
http.addFilterBefore(myFilterSecurityInterceptor,FilterSecurityInterceptor.class);
}
复制代码
Spring Security受权过程当中,能够会涉主要涉及了上面上面所述的组件,其中主要的仍是跟着源码多跑几遍,了解其中的原理,才能更加流畅的码代码。到此为止写完Spring Security的认证和受权分析流程,接下来会结合前面小节,写一个Spring security完美的权限管理系统。
最后可关注公众号,一块儿学习。加群,天天会分享干货,还有学习视频领取!