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:
私は今問題を解決するために非常に近いです、残っている唯一のものはこれを解決することです:
誰かがその質問で私を助けることができれば、私はこれを解決するために私が経験したことを最終的に書くことができます。
ここに待望の答えがあります:
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
sは後者で定義されており、上記のメソッドは処理されていません。バグである可能性があります(そうでない場合は、件名の説明は大歓迎です)。
Spring Security 4.2.1.RELEASEを使用するようにアプリケーションをアップグレードした後、アップグレード前に正常に機能していたすべての_@PreAuthorize
_アノテーション付きメソッドで予期しないアクセスが拒否されるようになりました。私は春のセキュリティコードをデバッグしましたが、以下のコードに示すように、デフォルトのプレフィックスを空に設定したという事実に関係なく、チェックされるすべてのロールにデフォルトの文字列「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
_の最初の初期化中に、クラス_org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer
_の宣言されたBeanをチェックしてデフォルトのプレフィックスを探すことがわかりました。この初期化子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を使用していて、GrantedAuthorityDefaults Beanも使用しているので、私と同じSpring Securityバージョンを使用している場合-4.2.1.RELEASE同じ問題。
DB内のデータと構成は適切に見えます。私はいつも@PostFilter("hasPermission(filterObject, 'READ')")
を使っています。
UserDetailsを拡張するユーザークラスが、dbにあるgetUsername()を介して同じユーザー名を返していることを確認します。セキュリティとアプリが同じコンテキストにあることを確認することと一緒に。
hasPermission メソッドは Authentication オブジェクトを1番目のパラメーターとして取得します。
boolean hasPermission(Authentication authentication,
Object targetDomainObject,
Object permission)
Authenticationオブジェクトは、通常 sernamePasswordAuthenticationToken の実装クラスです。そのため、getPrincipal()メソッドは、DBと同じものを返すgetUserName()メソッドを持つオブジェクトを返す必要があります。
PrincipalSid を見てください。
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();
}
}