我在Spring Boot应用程序中配置了ACL。 ACL配置如下:
@Configuration
@ComponentScan(basePackages = "com.company")
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class ACLConfigration extends GlobalMethodSecurityConfiguration {
@Autowired
DataSource dataSource;
@Bean
public EhCacheBasedAclCache aclCache() {
return new EhCacheBasedAclCache(aclEhCacheFactoryBean().getObject(), permissionGrantingStrategy(), aclAuthorizationStrategy());
}
@Bean
public EhCacheFactoryBean aclEhCacheFactoryBean() {
EhCacheFactoryBean ehCacheFactoryBean = new EhCacheFactoryBean();
ehCacheFactoryBean.setCacheManager(aclCacheManager().getObject());
ehCacheFactoryBean.setCacheName("aclCache");
return ehCacheFactoryBean;
}
@Bean
public EhCacheManagerFactoryBean aclCacheManager() {
return new EhCacheManagerFactoryBean();
}
@Bean
public DefaultPermissionGrantingStrategy permissionGrantingStrategy() {
ConsoleAuditLogger consoleAuditLogger = new ConsoleAuditLogger();
return new DefaultPermissionGrantingStrategy(consoleAuditLogger);
}
@Bean
public AclAuthorizationStrategy aclAuthorizationStrategy() {
return new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("ROLE_ACL_ADMIN"));
}
@Bean
public LookupStrategy lookupStrategy() {
return new BasicLookupStrategy(dataSource, aclCache(), aclAuthorizationStrategy(), new ConsoleAuditLogger());
}
@Bean
public JdbcMutableAclService aclService() {
return new JdbcMutableAclService(dataSource, lookupStrategy(), aclCache());
}
@Bean
public DefaultMethodSecurityExpressionHandler defaultMethodSecurityExpressionHandler() {
return new DefaultMethodSecurityExpressionHandler();
}
@Override
public MethodSecurityExpressionHandler createExpressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler = defaultMethodSecurityExpressionHandler();
expressionHandler.setPermissionEvaluator(new AclPermissionEvaluator(aclService()));
expressionHandler.setPermissionCacheOptimizer(new AclPermissionCacheOptimizer(aclService()));
return expressionHandler;
}
}
参考文献:
,安全配置如下:
@Configuration
@EnableWebSecurity
public class CustomSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Bean
public AuthenticationEntryPoint entryPoint() {
return new LoginUrlAuthenticationEntryPoint("/authenticate");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.authorizeRequests()
.antMatchers("/authenticate/**").permitAll()
.anyRequest().fullyAuthenticated()
.and().requestCache().requestCache(new NullRequestCache())
.and().addFilterBefore(authenticationFilter(), CustomUsernamePasswordAuthenticationFilter.class);
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
}
@Bean
public CustomUsernamePasswordAuthenticationFilter authenticationFilter()
throws Exception {
CustomUsernamePasswordAuthenticationFilter authenticationFilter = new CustomUsernamePasswordAuthenticationFilter();
authenticationFilter.setUsernameParameter("username");
authenticationFilter.setPasswordParameter("password");
authenticationFilter.setFilterProcessesUrl("/authenticate");
authenticationFilter.setAuthenticationSuccessHandler(new CustomAuthenticationSuccessHandler());
authenticationFilter.setAuthenticationFailureHandler(new CustomAuthenticationFailureHandler());
authenticationFilter.setAuthenticationManager(authenticationManagerBean());
return authenticationFilter;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
我的CustomAuthenticationProvider
课程:
@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
@Autowired
private UsersService usersService;
@Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
User user = usersService.findOne(username);
if(user != null && usersService.comparePassword(user, password)){
return new UsernamePasswordAuthenticationToken(
user.getUsername(),
user.getPassword(),
AuthorityUtils.commaSeparatedStringToAuthorityList(
user.getUserRoles().stream().collect(Collectors.joining(","))));
} else {
return null;
}
}
@Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
这是我的CustomUsernamePasswordAuthenticationToken
:
public class CustomUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
if(!request.getMethod().equals("POST"))
throw new AuthenticationServiceException(String.format("Authentication method not supported: %s", request.getMethod()));
try {
CustomUsernamePasswordAuthenticationForm form = new ObjectMapper().readValue(request.getReader(), CustomUsernamePasswordAuthenticationForm.class);
String username = form.getUsername();
String password = form.getPassword();
if(username == null)
username = "";
if(password == null)
password = "";
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password);
setDetails(request, token);
return getAuthenticationManager().authenticate(token);
} catch (IOException exception) {
throw new CustomAuthenticationException(exception);
}
}
private class CustomAuthenticationException extends RuntimeException {
private CustomAuthenticationException(Throwable throwable) {
super(throwable);
}
}
}
除了上述内容之外,我还有CustomAuthenticationFailureHandler
,CustomAuthenticationSuccessHandler
,CustomNoRedirectStrategy
和CustomUsernamePasswordAuthenticationForm
,我为了这个问题的长度而跳过了。
我正在使用可以找到here的MySQL架构。
我正在为我的acl相关表添加条目,如下所示:
INSERT INTO acl_class VALUES (1, com.company.project.domain.users.User)
INSERT INTO acl_sid VALUES (1, 1, "demo")
(我的用户名为demo
)
INSERT INTO acl_object_identity VALUES (1, 1, 1, NULL, 1, 0)
INSERT INTO acl_entry VALUES (1, 1, 1, 1, 1, 1, 1, 1)
但我得到的只是:
Denying user demo permission 'READ' on object com.company.project.domain.users.User@4a49e9b4
在我的
中@PostFilter("hasPermission(filterObject, 'READ')")
我怀疑这里有几个问题:
hasPermission
表达式:我已将其替换为'READ'和'1',但无论如何。expressionHandler.setPermissionEvaluator(new AclPermissionEvaluator(aclService()));
足够了?更新
使用@PostFilter
的示例方法:
@RequestMapping(method = RequestMethod.GET)
@PostFilter("hasPermission(filterObject, 'READ')")
List<User> find(@Min(0) @RequestParam(value = "limit", required = false, defaultValue = "10") Integer limit,
@Min(0) @RequestParam(value = "page", required = false, defaultValue = "0") Integer page,
@RequestParam(value = "email", required = false) String email,
@RequestParam(value = "firstName", required = false) String firstName,
@RequestParam(value = "lastName", required = false) String lastName,
@RequestParam(value = "userRole", required = false) String userRole) {
return usersService.find(
limit,
page,
email,
firstName,
lastName,
userRole);
}
更新#2:
现在的问题反映了在身份验证/授权/ ACL方面设置的所有内容。
更新#3:
我现在非常接近解决这个问题,唯一剩下的就是解决这个问题:
如果有人可以帮我解决这个问题,我终于可以写一下我已经解决的问题。
答案 0 :(得分:6)
我升级了我的应用程序以使用Spring Security 4.2.1.RELEASE然后我开始在所有@PreAuthorize
带注释的方法中遇到意外访问被拒绝,这在升级之前工作正常。
我调试了spring安全代码,我意识到问题是所有要检查的角色都带有一个默认字符串“ROLE_”,无论我将默认前缀设置为空,如下面的代码所示。
auth.ldapAuthentication()
.groupSearchBase(ldapProperties.getProperty("groupSearchBase"))
.groupRoleAttribute(ldapProperties.getProperty("groupRoleAttribute"))
.groupSearchFilter(ldapProperties.getProperty("groupSearchFilter"))
//this call used to be plenty to override the default prefix
.rolePrefix("")
.userSearchBase(ldapProperties.getProperty("userSearchBase"))
.userSearchFilter(ldapProperties.getProperty("userSearchFilter"))
.contextSource(this.ldapContextSource);
我的所有控制器方法都使用@PreAuthorize("hasRole('my_ldap_group_name')")
进行了注释,但是,框架没有考虑我的空角色前缀设置,因此它使用 ROLE_my_ldap_group_name 来检查实际角色。
在深入研究框架的代码之后,我意识到类org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler
仍然将默认角色前缀设置为"ROLE_"
。我跟进了它的值的来源,我发现它首先检查类org.springframework.security.config.core.GrantedAuthorityDefaults
的声明bean,以便在bean org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer
的第一次初始化期间查找默认前缀,但是,这个初始化器bean无法找到它声明,它最终使用上述默认前缀。
我认为这不是预期的行为:Spring Security应该从ldapAuthentication考虑相同的rolePrefix,但是,为了解决这个问题,有必要将bean org.springframework.security.config.core.GrantedAuthorityDefaults
添加到我的应用程序上下文中(我是使用基于注释的配置),如下所示:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class CesSecurityConfiguration extends WebSecurityConfigurerAdapter {
private static final String ROLE_PREFIX = "";
//... ommited code ...
@Bean
public GrantedAuthorityDefaults grantedAuthorityDefaults() {
return new GrantedAuthorityDefaults(ROLE_PREFIX);
}
}
也许你遇到了同样的问题 - 我可以看到你正在使用DefaultMethodSecurityExpressionHandler并且它也使用bean GrantedAuthorityDefaults,所以如果你使用的是与我相同的Spring Security版本 - 4.2.1.RELEASE你是可能遇到了同样的问题。
答案 1 :(得分:3)
这是一个漫长的等待答案:
documentation清楚地描述了:
要使用hasPermission()表达式,必须显式配置 应用程序上下文中的PermissionEvaluator。这看起来 像这样的东西:
基本上我在AclConfiguration
扩展GlobalMethodSecurityConfiguration
:
@Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
expressionHandler.setPermissionEvaluator(new AclPermissionEvaluator(aclService()));
expressionHandler.setPermissionCacheOptimizer(new AclPermissionCacheOptimizer(aclService()));
return expressionHandler;
}
Spring没有处理过这个问题!
我必须将AclConfig
和GlobalMethodSecurityConfiguration
分开。如果后者中定义了@Bean
,则上述方法不会被处理,这可能是一个错误(如果没有,欢迎任何关于主题的澄清)。
答案 2 :(得分:0)
您在数据库和配置中的数据看起来不错。我一直使用@PostFilter("hasPermission(filterObject, 'READ')")
。
我要检查以确保扩展UserDetails的用户类通过db中的getUsername()返回相同的用户名。同时检查以确保您的安全性和应用程序处于相同的上下文中。
hasPermission方法将Authentication对象作为第一个参数。
boolean hasPermission(Authentication authentication,
Object targetDomainObject,
Object permission)
Authentication对象是一个实现类,通常为UsernamePasswordAuthenticationToken。因此,getPrincipal()方法需要返回一个具有getUserName()方法的对象,该方法返回与数据库中相同的内容。
public PrincipalSid(Authentication authentication) {
Assert.notNull(authentication, "Authentication required");
Assert.notNull(authentication.getPrincipal(), "Principal required");
if (authentication.getPrincipal() instanceof UserDetails) {
this.principal = ((UserDetails) authentication.getPrincipal()).getUsername();
}
else {
this.principal = authentication.getPrincipal().toString();
}
}