SpringSecurity原理剖析与权限系统设计 (2)

鉴权:权限拦截器AbstractSecurityInterceptor通过读取不同的SecurityMetadataSource加载需要被鉴权资源的描述信息ConfigAttribute,然后把认证信息Authentication、资源描述ConfigAttribute、资源对象本身传递给AccessDecisionManager进行表决。AccessDecisionManager内置了多个投票器AccessDecisionVoter,投票器会将鉴权信息中的ConfigAttribute转换为SpringEL的格式,通过表达式处理器SecurityExpressionHandler执行基于表达式的鉴权逻辑,鉴权逻辑会通过反射的方式转发到SecurityExpressionRoot的各个操作上去。

定制:通过WebSecurityConfigureAdapter可以定制HTTP安全配置HttpSecurity和认证管理器生成器AuthenticationManagerBuilder;通过AbstractPreAuthenticatedProcessingFilter可以定制预认证过滤器;通过UserDetailsManager和UserDetails接口可以对接自定义数据源;通过GrantedAuthority定制权限信息;通过PermissionEvaluator可以定制自定义领域模型的访问控制逻辑。

四、应用集成

理清Spring Security的定制点后,就可以在系统内部集成Spring Security了。

这里使用预认证的方式,以适配第三方认证系统。AbstractPreAuthenticatedProcessingFilter提供了预认证的扩展点,基于该抽象类实现一个自定义认证过滤器。

public class MyPreAuthFilter extends AbstractPreAuthenticatedProcessingFilter { @Override protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) { // 从第三方系统获取用户ID return userId; } @Override protected Object getPreAuthenticatedCredentials(HttpServletRequest request) { return ""; } }

Spring Security会根据预认证过滤器getPreAuthenticatedPrincipal返回的用户ID信息,加载用户角色等初始信息。这里需要实现UserDetailsManager接口,提供用户信息管理器。

@Service public class MyUserManager implements UserDetailsManager { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 从数据库加载用户信息 return user; } // 其他管理接口 }

UserDetails内包含了GrantedAuthority接口类型的权限信息抽象,一般可以基于它自定义角色和权限。Spring Security使用一种接口形式表达角色和权限,角色和权限的差别是角色的ID是以"ROLE_"为前缀。

public class MyRole implements GrantedAuthority { private final String role; @Override public String getAuthority() { return "ROLE_" + role; } } public class MyAuthority implements GrantedAuthority { private final String authority; @Override public String getAuthority() { return authority; } }

接下来注册自定义认证过滤器和用户管理器,这里需要实现WebSecurityConfigurerAdapter进行Web安全配置。

@EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true, mode = AdviceMode.PROXY) public class MySecurityConfig extends WebSecurityConfigurerAdapter { @Autowired UserDetailsManager userDetailsManager; @Bean protected AuthenticationProvider createPreAuthProvider() { // 注册用户管理器 PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider(); provider.setPreAuthenticatedUserDetailsService(new UserDetailsByNameServiceWrapper<>(userDetailsManager)); return provider; } @Override protected void configure(HttpSecurity http) throws Exception { // 注册预认证过滤器 http.addFilter(new MyPreAuthFilter(authenticationManager())); } }

这样,最简单的Spring Security框架集成内系统内部已经完成了。在系统的任意服务接口上可以使用如下方式进行鉴权。

public interface MyService { @PreAuthorize("hasAuthority('QUERY')") Object getById(String id); @PreAuthorize("hasRole('ADMIN')") void deleteById(String id); }

PreAuthorize注解表示调用前鉴权,Spring使用默认使用动态代理技术生成鉴权逻辑。注解内配置了SpringEL表达式来定制鉴权方式。上述代码中,hasAuthority会检查用户是否有QUERY权限,hasRole会检查用户是否有ADMIN角色。

使用动态代理的方式进行AOP,只允许在接口层面进行权限拦截,如果想在任意的方法上进行权限拦截,那么就需要借助于AspectJ的方式进行AOP。首先将注解EnableGlobalMethodSecurity的mode设置为AdviceMode.ASPECTJ,然后添加JVM启动参数,这样就可以在任意方法上使用Spring Security的注解了。

-javaagent:/path/to/org/aspectj/aspectjweaver/1.9.4/aspectjweaver-1.9.4.jar

以上还是只是以用户的身份信息(角色/权限)进行权限,灵活度有限,也发挥不了Spring Security的数据鉴权的能力。要使用数据鉴权,需要实现一个Spring Bean。

@Component public class MyPermissionEvaluator implements PermissionEvaluator { @Override public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) { // 自定义数据鉴权 return false; } @Override public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) { // 自定义数据鉴权 return false; } }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zyywwy.html