当前位置: 首页 > 科技观察

SpringSecurity原理分析与权限系统设计

时间:2023-03-14 16:05:48 科技观察

SpringSecutity和ApacheShiro是Java领域的两大主流开源安全框架,也是权限系统设计的主要技术选型。本文主要介绍了SpringSecutity的实现原理,并基于SpringSecutity设计了一个基于RBAC的权限系统。1.技术选型为什么选用SpringSecutity作为权限系统的技术选型,主要考虑以下几个方面:数据鉴权能力:SpringSecutity支持数据鉴权,即细粒度的权限控制。Spring生态基础:SpringSecutity可与Spring生态无缝集成。丰富的认证能力:SpringSecutity支持多种认证方式,例如可以与第三方认证系统集成的预认证方式。2.核心架构权限系统一般包括两个核心模块:Authentication和Authorization。认证:认证模块负责验证用户身份的合法性,生成认证令牌,保存在服务器会话中(如TLS)。身份验证:身份验证模块负责从服务器会话中获取用户身份信息,并将权限与访问的资源进行比较。SpringSecurity官方给出的核心架构图如下:核心架构解读:AuthenticationManager:负责认证管理,解析用户登录信息(封装在Authentication中),读取用户、角色、权限信息进行认证,并进行认证结果在身份验证中回填,保存在SecurityContext中。AccessDecisionManager:负责认证投票,汇总投票者结果,实现一票通过(默认)、多票通过、一票否决策略。SecurityInterceptor:负责权限拦截,包括WebURL拦截和方法调用拦截。通过ConfigAttributes获取资源描述信息,借助AccessDecisionManager进行鉴权拦截。SecurityContext:安全上下文,保存认证结果。提供三种策略:全局上下文、线程继承上下文和线程独立上下文(默认)。认证:认证信息,保存用户的身份标识、权限列表、证书、认证通过标识等信息。SecuredResource:安全控制的资源,如WebURL、用户、角色、自定义域对象等。ConfigAttributes:资源属性配置,描述安全管控资源的信息,为SecurityInterceptor提供拦截逻辑的输入。3.设计原理通过源码分析,我将SpringSecurity的核心领域模型设计整理如下:全局抽象模型解读:配置:AuthenticationConfiguration负责认证系统的全局配置,GlobalMethodSecurityConfiguration负责用于方法调用拦截的全局配置。构建:AuthenticationConfiguration通过AuthenticationManagerBuilder构建认证管理器AuthenticationManager,GlobalMethodSecurityConfiguration会自动初始化AbstractSecurityInterceptor拦截方法调用。Web拦截:HttpSecurity配置Web安全,内置大量GenericFilterBean过滤器拦截URL。负责认证的过滤器会通过AuthenticationManager进行认证,并将认证结果保存到SecurityContext中。方法拦截:Spring使用AOP技术(cglib/aspectj)拦截@PreAuthorize、@PreFilter、@PostAuthorize、@PostFilter等注解标记的方法,通过AbstractSecurityInterceptor调用AuthenticationManager进行身份认证(如果需要)。Authentication:认证管理器AuthenticationManager内置了多个认证器AuthenticationProvider,只要其中一个通过认证就认证成功。不同的AuthenticationProvider获取自己需要的信息(HTTP请求、数据库查询、远程服务等)进行认证,认证结果都封装在Authentication中。需要加载用户、角色、权限信息的认证器(如密码认证、预认证等)需要连接到UserDetailsManager接口实现用户CRUD功能。Authentication:权限拦截器AbstractSecurityInterceptor通过读取不同的SecurityMetadataSources加载待认证资源的描述信息ConfigAttribute,然后将认证信息Authentication、资源描述ConfigAttribute以及资源对象本身传递给AccessDecisionManager进行投票。AccessDecisionManager有多个内置投票器,AccessDecisionVoter。选民将认证信息中的ConfigAttribute转换成SpringEL格式,通过表达式处理器SecurityExpressionHandler执行基于表达式的认证逻辑。认证逻辑会转发到SecurityExpressionRoot的各种操作上去。自定义:HTTP安全配置HttpSecurity和认证管理器生成器AuthenticationManagerBuilder可以通过WebSecurityConfigureAdapter进行自定义;可以通过AbstractPreAuthenticatedProcessingFilter自定义预认证过滤器;可以通过UserDetailsManager和UserDetails接口连接自定义数据源;可以通过GrantedAuthority自定义权限信息;自定义可以通过PermissionEvaluator定义领域模型的访问控制逻辑。四、应用集成在明确了SpringSecurity的定制点之后,就可以在系统内部集成SpringSecurity了。这里采用前置认证的方式来适配第三方认证系统。AbstractPreAuthenticatedProcessingFilter提供了一个预认证扩展点,基于这个抽象类来实现一个自定义的认证过滤器。publicclassMyPreAuthFilterextendsAbstractPreAuthenticatedProcessingFilter{@OverrideprotectedObjectgetPreAuthenticatedPrincipal(HttpServletRequestrequest){//从第三方系统获取用户IDreturnuserId;}@OverrideprotectedObjectgetPreAuthenticatedCredentials(HttpServletRequestrequest){return"";}}SpringSecurity会根据预认证过滤器getPreAuthenticatedPrincipal返回的用户ID信息,加载用户角色等初始信息。这里需要实现UserDetailsManager接口,提供一个用户信息管理器。@ServicepublicclassMyUserManagerimplementsUserDetailsManager{@OverridepublicUserDetailsloadUserByUsername(Stringusername)throwsUsernameNotFoundException{//从数据库中加载用户信息returnuser;}//其他管理接口}UserDetails包含GrantedAuthority接口类型的权限信息抽象,一般可以基于它自定义角色和权限.SpringSecurity使用一个接口来表达角色和权限。角色和权限的区别在于角色ID以“ROLE_”为前缀。publicclassMyRoleimplementsGrantedAuthority{privatefinalStringrole;@OverridepublicStringgetAuthority(){return"ROLE_"+role;}}publicclassMyAuthorityimplementsGrantedAuthority{privatefinalStringauthority;@OverridepublicStringgetAuthority(){returnauthority;}}接下来,您需要在此处为??网络广告注册自定义身份验证过滤器和用户管理器。安全配置。@EnableWebSecurity@EnableGlobalMethodSecurity(prePostEnabled=true,mode=AdviceMode.PROXY)publicclassMySecurityConfigextendsWebSecurityConfigurerAdapter{@AutowiredUserDetailsManageruserDetailsManager;@BeanprotectedAuthenticationProvidercreatePreAuthProvider(){//注册用户管理器PreAuthenticatedAuthenticationProviderprovider=newPreAuthenticatedAuthenticationProvider();provider.setPreAuthenticatedUserDetailsS??ervice(newUserDetailsByNameServiceWrapper<>(userDetailsManager));returnprovider;}@Overrideprotectedvoidconfigure(HttpSecurityhttp)throwsException{//注册预认证过滤器http.addFilter(newMyPreAuthFilter(authenticationManager()));}}这样一个最简单的SpringSecurity框架集成系统就完成了。在系统的任何服务接口上都可以使用以下方法进行认证。publicinterfaceMyService{@PreAuthorize("hasAuthority('QUERY')")ObjectgetById(Stringid);@PreAuthorize("hasRole('ADMIN')")voiddeleteById(Stringid);}PreAuthorize注解表示调用前鉴权,Spring默认使用dynamic代理技术生成认证逻辑。在注解中配置SpringEL表达式,自定义认证方式。上面代码中,hasAuthority会检查用户是否有QUERY权限,hasRole会检查用户是否有ADMIN角色。AOP使用动态代理只允许在接口层面进行权限拦截。如果要对任何方法进行权限拦截,就需要使用AspectJ进行AOP。首先将注解EnableGlobalMethodSecurity的mode设置为AdviceMode.ASPECTJ,然后添加JVM启动参数,这样SpringSecurity注解就可以用在任何方法上。javaagent:/path/to/org/aspectj/aspectjweaver/1.9.4/aspectjweaver-1.9.4.jar或以上只是基于用户的身份信息(角色/权限),灵活性有限,SpringSecurity不能使用了验证数据的能力。要使用数据认证,你需要实现一个SpringBean。@ComponentpublicclassMyPermissionEvaluatorimplementsPermissionEvaluator{@OverridepublicbooleanhasPermission(Authenticationauthentication,ObjecttargetDomainObject,Objectpermission){//自定义数据鉴权returnfalse;}@OverridepublicbooleanhasPermission(Authenticationauthentication,SerializabletargetId,StringtargetType,Objectpermission){//自定义数据鉴权returnfalse;}}PermissionEvaluator会被自动注册SpringSecurity框架,并允许在注解中使用以下方法进行身份验证。@PreAuthorize("hasPermission(#id,'QUERY')")Objectfunc1(Stringid){}@PreAuthorize("hasPermission(#id,'TABLE','QUERY')")Objectfunc2(Stringid){}其中,func1注解表示用户是否对该id有QUERY权限,代码逻辑路由到MyPermissionEvaluator第一个接口。func2的注解表示用户是否对TABLE类型id有QUERY权限,代码逻辑路由到MyPermissionEvaluator的第二个接口。PermissionEvaluator为权限系统中的数据鉴权提供了一个扩展点,后面会介绍如何使用这个扩展点来定制基于RBAC的权限系统。五、权限体系构建基于RBAC(RoleBasedAccessControl)的权限体系,需要明确用户、角色、权限、资源等核心概念类的含义以及它们之间的关系。资源:权限系统中需要安全控制的对象,一般是系统中的数据或函数。Permissions:描述了对资源的操作的抽象,一般是一个action。授权:是权限和资源的组合,代表对资源的一种操作。作用:描述了一组权限,代表了一类特殊概念的一组功能。用户:权限系统的主体,一般是当前系统的访问用户,用户可以有多个角色。下面是我们设计的基于RABC的权限核心领域模型:一般系统中需要权限控制的资源是用户不能自定义的,因为资源会耦合很多业务逻辑,所以我们提供了一个自资源工厂,通过配置以标准化方式构建业务模块所需的资源。可以通过相应的管理器查询和更新用户、角色、权限和授权记录。另外,资源抽象允许表达资源继承和组合关系,进而表达更复杂的资源模型。统一资源认证的流程如下:在进行认证时,首先判断资源是原子资源还是复合资源。对于原子资源,先检查是否有授权记录,再检查角色预授权是否包含当前授权。如果有一个,它就会成功。对于没有授权记录和角色预授权的原子资源,尝试用父资源(如果有的话)代替认证,否则认证失败。对于组合资源,先展开资源,得到子资源列表。遍历子资源列表,依次对子资源进行鉴权。子资源认证结果汇总后,合并资源认证结果。综上所述,基于统一的资源抽象和资源配置构建,可以实现资源的统一构建,进而实现统一认证。6.总结与回顾本文从SpringSecurity的架构和原理出发,描述了认证和认证模块的开源安全框架的设计思路和细节。还提供了在系统中集成SpringSecurity的方法,并结合RBAC通用权限体系模型讨论了统一资源构建和统一认证的设计与实现。如果你也需要设计一个新的权限系统,希望本文对你有所帮助。