Spring 安全性 + LDAP + CustomLdapAuthoritiesPopulator + RememberMe
Spring Security + LDAP + CustomLdapAuthoritiesPopulator + RememberMe
我在 spring 安全方面有点问题 :)
我的目标是什么:
使用从数据库中获取的自定义角色配置 LDAP 身份验证,并记住我的功能。
做了什么:
- LDAP 验证:确定
- 来自数据库的 AD 用户自定义角色:OK
- 记住我:失败
我的问题是:
'Remember me' 工作正常,persistent_logins table 创建成功,它可以很好地存储令牌。但是当用户 returns 访问网站时,spring 显示 'not authorized' 页面。
我认为这是因为 'Remember me' 对我的自定义角色以及从 LDAP 获取角色一无所知。
问题是:如何告诉'Remember me'通过我的CustomLdapAuthoritiesPopulator获得角色?
我的applicationContext.xml
<bean id="contextSource" class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
<constructor-arg value="ldap://ldap.forumsys.com:389"/>
<property name="userDn" value="cn=read-only-admin,dc=example,dc=com"/>
<property name="password" value="password"/>
</bean>
<bean name="myDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/db"/>
<property name="username" value="username"/>
<property name="password" value="password"/>
</bean>
<bean id="ldapAuthProvider" class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider">
<constructor-arg>
<bean class="org.springframework.security.ldap.authentication.BindAuthenticator">
<constructor-arg ref="contextSource"/>
<property name="userDnPatterns">
<list>
<value>uid={0},dc=example,dc=com</value>
</list>
</property>
</bean>
</constructor-arg>
<constructor-arg>
<bean class="my.extra.CustomLdapAuthoritiesPopulator"/>
</constructor-arg>
</bean>
<bean id="tokenRepository"
class="org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl">
<property name="createTableOnStartup" value="false"/>
<property name="dataSource" ref="myDataSource"/>
</bean>
<security:authentication-manager>
<security:authentication-provider ref="ldapAuthProvider"/>
</security:authentication-manager>
<security:http auto-config="true" use-expressions="true">
<security:access-denied-handler error-page="/403"/>
<security:intercept-url pattern="/login*" access="permitAll()"/>
<security:intercept-url pattern="/favicon.ico" access="permitAll()"/>
<security:intercept-url pattern="/resources/**" access="permitAll()"/>
<security:intercept-url pattern="/**" access="hasRole('ROLE_USER')"/>
<security:form-login login-page='/login' login-processing-url="/j_spring_security_check"
default-target-url="/" authentication-failure-url="/login?fail"/>
<security:remember-me key="_spring_security_remember_me" token-validity-seconds="14400"
token-repository-ref="tokenRepository"
user-service-ref="ldapUserService"/>
</security:http>
<security:ldap-user-service id="ldapUserService" server-ref="contextSource"
group-search-base="dc=example,dc=com"
group-search-filter="ou={0})"
user-search-base="dc=example,dc=com"
user-search-filter="uid={0}"/>
在调试期间,当用户 returns 时,未调用 CustomLdapAuthoritiesPopulator。
我添加了代码来检查用户的角色(在欢迎页面和自定义 403 页面上)。
Collection<? extends GrantedAuthority> roles = SecurityContextHolder.getContext().getAuthentication().getAuthorities();
roles.forEach(System.out::println);
用户登录后,欢迎页面显示"ROLE_USER"、"ROLE_ADMIN"
用户returns返回后,403页面显示“”; (无)
If you are using an authentication provider which doesn't use a UserDetailsService
(for example, the LDAP provider) then it won't work unless you also have a UserDetailsService bean in your application context.
尝试在您的 applicationContext.xml
中添加以下 :-
添加RoleVoter。来自 Spring 文档
Votes if any ConfigAttribute.getAttribute()
starts with a prefix indicating that it is a role. The default prefix string is ROLE_
, but this may be overridden to any value. It may also be set to empty, which means that essentially any attribute will be voted on. As described further below, the effect of an empty prefix may not be quite desirable.
Abstains from voting if no configuration attribute commences with the role prefix. Votes to grant access if there is an exact matching GrantedAuthority
to a ConfigAttribute
starting with the role prefix. Votes to deny access if there is no exact matching GrantedAuthority to a ConfigAttribute starting with the role prefix.
All comparisons and prefixes are case sensitive.
<bean id="roleVoter"
class="org.springframework.security.access.vote.RoleVoter" p:rolePrefix="" />
添加AuthenticatedVoter。来自 Soring 文档
Votes if a ConfigAttribute.getAttribute()
of IS_AUTHENTICATED_FULLY
or IS_AUTHENTICATED_REMEMBERED
or IS_AUTHENTICATED_ANONYMOUSLY
is present. This list is in order of most strict checking to least strict checking.
The current Authentication will be inspected to determine if the principal has a particular level of authentication. The "FULLY" authenticated option means the user is authenticated fully (i.e. AuthenticationTrustResolver.isAnonymous(Authentication)
is false and AuthenticationTrustResolver.isRememberMe(Authentication)
is false). The "REMEMBERED"
will grant access if the principal was either authenticated via remember-me OR is fully authenticated. The "ANONYMOUSLY"
will grant access if the principal was authenticated via remember-me, OR anonymously, OR via full authentication.
All comparisons and prefixes are case sensitive.
<bean id="authVoter"
class="org.springframework.security.access.vote.AuthenticatedVoter">
</bean>
现在,配置一个 Spring AccessDecisionManager
来利用上面的两个投票器,以便 确定用户是否被授予访问资源的正确权限.
<bean id="accessDecisionManager" class="org.springframework.security.access.vote.ConsensusBased">
<property name="allowIfAllAbstainDecisions" value="false" />
<property name="decisionVoters">
<list>
<ref bean="roleVoter" />
<ref bean="authVoter" />
</list>
</property>
</bean>
更多详情:访问Spring security 3 remember-me with LDAP authentication
编辑 1:
- Spring Security with LDAP and Database roles
- Spring Security 3.1 - Implement UserDetailsService with Spring Data JPA
编辑 2:
以下来源最初发布于 Configuring Spring Security Form Login with Remember-Me Enabled。
创建自定义 RememberMeProcessingFilter:
public class MyRememberMeProcessingFilter extends RememberMeProcessingFilter {
private myService;
@Override
protected void onSuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, Authentication authResult) {
// perform some custom logic when the user has been 'remembered' & authenticated - e.g. update a login count etc
this.myService.doSomeCustomBusinessLogic(authResult.getName());
super.onSuccessfulAuthentication(request, response, authResult);
}
}
RememberMeProcessingFilter
to include any custom business logic you wish to run when a user returns to your site and is ‘remembered’ by the application.
别忘了加上:
<custom-authentication-provider />
这确保记住我实际上用作身份验证提供程序 - 即当您的用户 returns 之前要求被记住时,这会将记住我添加到检查用户是否经过身份验证的提供商列表。
使用错误的解决方案
<security:ldap-user-service/>
如果您想为 'Remember me' 功能应用数据库中的自定义角色。
仍然需要实现自定义 UserDetailsService 并从“记住我”部分引用它。
<security:http>
....
<security:remember-me key="_spring_security_remember_me" token-validity-seconds="14400"
token-repository-ref="tokenRepository"
user-service-ref="rememberMeUserDetailsService"/>
</security:http>
<bean id="rememberMeUserDetailsService" class="gpb.extra.RememberMeUserDetailsService"/>
它有点棘手,但有效,我的数据库存储每个用户名,因为它存储角色。所以,我可以检查任何没有 LDAP 的用户。
package gpb.extra;
import gpb.database.models.User;
import gpb.database.utils.HibernateUtils;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
public class RememberMeUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Collection<GrantedAuthority> authorities = new HashSet<>();
List<User> users = HibernateUtils.get(User.class, username, "username");
if (users.size() == 0) {
throw new UsernameNotFoundException("User not found");
} else {
User existingUser = users.get(0);
if (!existingUser.getRoles().isEmpty()) {
List<String> roles = Arrays.asList(existingUser.getRoles().split(","));
roles.forEach(t -> authorities.add(new SimpleGrantedAuthority(t.trim())));
}
}
boolean enabled = true;
boolean accountNonExpired = true;
boolean credentialsNonExpired = true;
boolean accountNonLocked = true;
return new org.springframework.security.core.userdetails.User(
username, "password", enabled, accountNonExpired,
credentialsNonExpired, accountNonLocked, authorities);
}
}
代码演示了主要思想。
非常感谢@OO7 为我指明了正确的道路并提供了惊人的帮助:)
我在 spring 安全方面有点问题 :)
我的目标是什么: 使用从数据库中获取的自定义角色配置 LDAP 身份验证,并记住我的功能。
做了什么:
- LDAP 验证:确定
- 来自数据库的 AD 用户自定义角色:OK
- 记住我:失败
我的问题是: 'Remember me' 工作正常,persistent_logins table 创建成功,它可以很好地存储令牌。但是当用户 returns 访问网站时,spring 显示 'not authorized' 页面。
我认为这是因为 'Remember me' 对我的自定义角色以及从 LDAP 获取角色一无所知。
问题是:如何告诉'Remember me'通过我的CustomLdapAuthoritiesPopulator获得角色?
我的applicationContext.xml
<bean id="contextSource" class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
<constructor-arg value="ldap://ldap.forumsys.com:389"/>
<property name="userDn" value="cn=read-only-admin,dc=example,dc=com"/>
<property name="password" value="password"/>
</bean>
<bean name="myDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/db"/>
<property name="username" value="username"/>
<property name="password" value="password"/>
</bean>
<bean id="ldapAuthProvider" class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider">
<constructor-arg>
<bean class="org.springframework.security.ldap.authentication.BindAuthenticator">
<constructor-arg ref="contextSource"/>
<property name="userDnPatterns">
<list>
<value>uid={0},dc=example,dc=com</value>
</list>
</property>
</bean>
</constructor-arg>
<constructor-arg>
<bean class="my.extra.CustomLdapAuthoritiesPopulator"/>
</constructor-arg>
</bean>
<bean id="tokenRepository"
class="org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl">
<property name="createTableOnStartup" value="false"/>
<property name="dataSource" ref="myDataSource"/>
</bean>
<security:authentication-manager>
<security:authentication-provider ref="ldapAuthProvider"/>
</security:authentication-manager>
<security:http auto-config="true" use-expressions="true">
<security:access-denied-handler error-page="/403"/>
<security:intercept-url pattern="/login*" access="permitAll()"/>
<security:intercept-url pattern="/favicon.ico" access="permitAll()"/>
<security:intercept-url pattern="/resources/**" access="permitAll()"/>
<security:intercept-url pattern="/**" access="hasRole('ROLE_USER')"/>
<security:form-login login-page='/login' login-processing-url="/j_spring_security_check"
default-target-url="/" authentication-failure-url="/login?fail"/>
<security:remember-me key="_spring_security_remember_me" token-validity-seconds="14400"
token-repository-ref="tokenRepository"
user-service-ref="ldapUserService"/>
</security:http>
<security:ldap-user-service id="ldapUserService" server-ref="contextSource"
group-search-base="dc=example,dc=com"
group-search-filter="ou={0})"
user-search-base="dc=example,dc=com"
user-search-filter="uid={0}"/>
在调试期间,当用户 returns 时,未调用 CustomLdapAuthoritiesPopulator。 我添加了代码来检查用户的角色(在欢迎页面和自定义 403 页面上)。
Collection<? extends GrantedAuthority> roles = SecurityContextHolder.getContext().getAuthentication().getAuthorities();
roles.forEach(System.out::println);
用户登录后,欢迎页面显示"ROLE_USER"、"ROLE_ADMIN"
用户returns返回后,403页面显示“”; (无)
If you are using an authentication provider which doesn't use a
UserDetailsService
(for example, the LDAP provider) then it won't work unless you also have a UserDetailsService bean in your application context.
尝试在您的 applicationContext.xml
中添加以下 :-
添加RoleVoter。来自 Spring 文档
Votes if any
ConfigAttribute.getAttribute()
starts with a prefix indicating that it is a role. The default prefix string isROLE_
, but this may be overridden to any value. It may also be set to empty, which means that essentially any attribute will be voted on. As described further below, the effect of an empty prefix may not be quite desirable.Abstains from voting if no configuration attribute commences with the role prefix. Votes to grant access if there is an exact matching
GrantedAuthority
to aConfigAttribute
starting with the role prefix. Votes to deny access if there is no exact matching GrantedAuthority to a ConfigAttribute starting with the role prefix.All comparisons and prefixes are case sensitive.
<bean id="roleVoter"
class="org.springframework.security.access.vote.RoleVoter" p:rolePrefix="" />
添加AuthenticatedVoter。来自 Soring 文档
Votes if a
ConfigAttribute.getAttribute()
ofIS_AUTHENTICATED_FULLY
orIS_AUTHENTICATED_REMEMBERED
orIS_AUTHENTICATED_ANONYMOUSLY
is present. This list is in order of most strict checking to least strict checking.The current Authentication will be inspected to determine if the principal has a particular level of authentication. The "FULLY" authenticated option means the user is authenticated fully (i.e.
AuthenticationTrustResolver.isAnonymous(Authentication)
is false andAuthenticationTrustResolver.isRememberMe(Authentication)
is false). The"REMEMBERED"
will grant access if the principal was either authenticated via remember-me OR is fully authenticated. The"ANONYMOUSLY"
will grant access if the principal was authenticated via remember-me, OR anonymously, OR via full authentication.All comparisons and prefixes are case sensitive.
<bean id="authVoter"
class="org.springframework.security.access.vote.AuthenticatedVoter">
</bean>
现在,配置一个 Spring AccessDecisionManager
来利用上面的两个投票器,以便 确定用户是否被授予访问资源的正确权限.
<bean id="accessDecisionManager" class="org.springframework.security.access.vote.ConsensusBased">
<property name="allowIfAllAbstainDecisions" value="false" />
<property name="decisionVoters">
<list>
<ref bean="roleVoter" />
<ref bean="authVoter" />
</list>
</property>
</bean>
更多详情:访问Spring security 3 remember-me with LDAP authentication
编辑 1:
- Spring Security with LDAP and Database roles
- Spring Security 3.1 - Implement UserDetailsService with Spring Data JPA
编辑 2:
以下来源最初发布于 Configuring Spring Security Form Login with Remember-Me Enabled。
创建自定义 RememberMeProcessingFilter:
public class MyRememberMeProcessingFilter extends RememberMeProcessingFilter {
private myService;
@Override
protected void onSuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, Authentication authResult) {
// perform some custom logic when the user has been 'remembered' & authenticated - e.g. update a login count etc
this.myService.doSomeCustomBusinessLogic(authResult.getName());
super.onSuccessfulAuthentication(request, response, authResult);
}
}
RememberMeProcessingFilter
to include any custom business logic you wish to run when a user returns to your site and is ‘remembered’ by the application.
别忘了加上:
<custom-authentication-provider />
这确保记住我实际上用作身份验证提供程序 - 即当您的用户 returns 之前要求被记住时,这会将记住我添加到检查用户是否经过身份验证的提供商列表。
使用错误的解决方案
<security:ldap-user-service/>
如果您想为 'Remember me' 功能应用数据库中的自定义角色。
仍然需要实现自定义 UserDetailsService 并从“记住我”部分引用它。
<security:http>
....
<security:remember-me key="_spring_security_remember_me" token-validity-seconds="14400"
token-repository-ref="tokenRepository"
user-service-ref="rememberMeUserDetailsService"/>
</security:http>
<bean id="rememberMeUserDetailsService" class="gpb.extra.RememberMeUserDetailsService"/>
它有点棘手,但有效,我的数据库存储每个用户名,因为它存储角色。所以,我可以检查任何没有 LDAP 的用户。
package gpb.extra;
import gpb.database.models.User;
import gpb.database.utils.HibernateUtils;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
public class RememberMeUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Collection<GrantedAuthority> authorities = new HashSet<>();
List<User> users = HibernateUtils.get(User.class, username, "username");
if (users.size() == 0) {
throw new UsernameNotFoundException("User not found");
} else {
User existingUser = users.get(0);
if (!existingUser.getRoles().isEmpty()) {
List<String> roles = Arrays.asList(existingUser.getRoles().split(","));
roles.forEach(t -> authorities.add(new SimpleGrantedAuthority(t.trim())));
}
}
boolean enabled = true;
boolean accountNonExpired = true;
boolean credentialsNonExpired = true;
boolean accountNonLocked = true;
return new org.springframework.security.core.userdetails.User(
username, "password", enabled, accountNonExpired,
credentialsNonExpired, accountNonLocked, authorities);
}
}
代码演示了主要思想。
非常感谢@OO7 为我指明了正确的道路并提供了惊人的帮助:)