Spring Security 控制授权基本原理
Spring Security 在安全认证的过程中,通过一系列的过滤器一层层对登录用户的身份进行认证。而关于权限认证的过滤器则在整个过滤器链的最后,是Spring Security整个认证过程的最后一道关卡,这个过滤器就是 FilterSecurityInterceptor。
控制授权流程源码解析
服务请求被 FilterSecurityInterceptor拦截后,
- 加载SecurityConfig安全配置,比如在HttpSecurity中的配置信息,
- 通过SecurityContextHolder加载登录用户的个人信息。
- 把安全配置信息,用户个人信息,登录请求信息交给AccessDecisionManager进行处理
- AccessDecisionManager调用AccessDecisionVoter进行投票处理,只要一票未过,则抛出异常,认证未通过
- 认证通过,则进行接口调用
权限表达式
Spring Security通过权限表达式进行权限配置
权限模块实现思路
- AuthorizeConfigProvider:授权配置提供器,各个模块和业务系统可以通过实现此接口向系统添加授权配置。
- AuthorizeConfigManager:授权信息管理器,用于收集系统中所有 AuthorizeConfigProvider 并加载其配置
实现思路
权限模块一般是基于RBAC(Role-Based Access Control )模型进行设计。每个模块可以通过实现AuthorizeConfigProvider接口自定义权限验证逻辑,最后交由 AuthorizeConfigManager实现类进行授权配置管理。每个应用通过实现权限模块和业务模块完成权限认证。
实现代码
/**
* 授权配置提供器,各个模块和业务系统可以通过实现此接口向系统添加授权配置。
*
*/
public interface AuthorizeConfigProvider {
/**
* @param config
* @return 返回的boolean表示配置中是否有针对anyRequest的配置。在整个授权配置中,
* 应该有且仅有一个针对anyRequest的配置,如果所有的实现都没有针对anyRequest的配置,
* 系统会自动增加一个anyRequest().authenticated()的配置。如果有多个针对anyRequest
* 的配置,则会抛出异常。
*/
boolean config(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry config);
}
/**
* 授权信息管理器
* 用于收集系统中所有 AuthorizeConfigProvider 并加载其配置
*
*/
public interface AuthorizeConfigManager {
/**
* @param config
*/
void config(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry config);
}
/**
* 核心模块的授权配置提供器,安全模块涉及的url的授权配置在这里。
*
*/
@Component
@Order(Integer.MIN_VALUE) //设置调用顺序优先
public class ImoocAuthorizeConfigProvider implements AuthorizeConfigProvider {
@Autowired
private SecurityProperties securityProperties;
@Override
public boolean config(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry config) {
config.antMatchers(SecurityConstants.DEFAULT_UNAUTHENTICATION_URL,
SecurityConstants.DEFAULT_SIGN_IN_PROCESSING_URL_MOBILE,
SecurityConstants.DEFAULT_SIGN_IN_PROCESSING_URL_OPENID,
SecurityConstants.DEFAULT_VALIDATE_CODE_URL_PREFIX + "/*",
securityProperties.getBrowser().getSignInPage(),
securityProperties.getBrowser().getSignUpUrl(),
securityProperties.getBrowser().getSession().getSessionInvalidUrl()).permitAll();
if (StringUtils.isNotBlank(securityProperties.getBrowser().getSignOutUrl())) {
config.antMatchers(securityProperties.getBrowser().getSignOutUrl()).permitAll();
}
return false;
}
}
/**
* 默认的授权配置管理器,再整个的授权中最后调用
*
*/
@Component
public class ImoocAuthorizeConfigManager implements AuthorizeConfigManager {
//启动后会自动注入所有AuthorizeConfigProvider实现类的Bean
@Autowired
private List<AuthorizeConfigProvider> authorizeConfigProviders;
/* (non-Javadoc)
* @see com.imooc.security.core.authorize.AuthorizeConfigManager#config(org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry)
*/
@Override
public void config(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry config) {
boolean existAnyRequestConfig = false;
String existAnyRequestConfigName = null;
for (AuthorizeConfigProvider authorizeConfigProvider : authorizeConfigProviders) {
boolean currentIsAnyRequestConfig = authorizeConfigProvider.config(config);
if (existAnyRequestConfig && currentIsAnyRequestConfig) {
throw new RuntimeException("重复的anyRequest配置:" + existAnyRequestConfigName + ","
+ authorizeConfigProvider.getClass().getSimpleName());
} else if (currentIsAnyRequestConfig) {
existAnyRequestConfig = true;
existAnyRequestConfigName = authorizeConfigProvider.getClass().getSimpleName();
}
}
if(!existAnyRequestConfig){
config.anyRequest().authenticated();
}
}
}
/**
* 权限模块授权配置
*/
@Component
@Order(Integer.MAX_VALUE) //控制anyRequest()要放在最后
public class RbacAuthorizeConfigProvider implements AuthorizeConfigProvider {
/* (non-Javadoc)
* @see com.imooc.security.core.authorize.AuthorizeConfigProvider#config(org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry)
*/
@Override
public boolean config(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry config) {
config
.antMatchers(HttpMethod.GET, "/fonts/**").permitAll()
.antMatchers(HttpMethod.GET,
"/**/*.html",
"/admin/me",
"/resource").authenticated()
.anyRequest()
.access("@rbacService.hasPermission(request, authentication)");//具体的鉴权逻辑
return true;
}
}
/**
* 具体的鉴权逻辑
*/
@Component("rbacService")
public class RbacServiceImpl implements RbacService {
private AntPathMatcher antPathMatcher = new AntPathMatcher();
@Override
public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
Object principal = authentication.getPrincipal();
boolean hasPermission = false;
if (principal instanceof Admin) {
//如果用户名是admin,就永远返回true
if (StringUtils.equals(((Admin) principal).getUsername(), "admin")) {
hasPermission = true;
} else {
// 读取用户所拥有权限的所有URL
Set<String> urls = ((Admin) principal).getUrls();
for (String url : urls) {
if (antPathMatcher.match(url, request.getRequestURI())) {
hasPermission = true;
break;
}
}
}
}
return hasPermission;
}
}