在 Spring Security 3 中动态创建新角色和权限

Posted

技术标签:

【中文标题】在 Spring Security 3 中动态创建新角色和权限【英文标题】:Creating New Roles and Permissions Dynamically in Spring Security 3 【发布时间】:2012-01-09 10:30:57 【问题描述】:

我在 Struts 2 + Spring IOC 项目中使用 Spring Security 3。

我在我的项目中使用了自定义过滤器、身份验证提供程序等。

你可以在这里看到我的security.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
  xmlns:beans="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:p="http://www.springframework.org/schema/p"
   xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
       http://www.springframework.org/schema/security
       http://www.springframework.org/schema/security/spring-security-3.1.xsd">


<global-method-security pre-post-annotations="enabled">
        <expression-handler ref="expressionHandler" />
</global-method-security>

<beans:bean id="expressionHandler"
        class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler" >
    <beans:property name="permissionEvaluator" ref="customPermissionEvaluator" />
</beans:bean>

<beans:bean class="code.permission.MyCustomPermissionEvaluator" id="customPermissionEvaluator" />

<!-- User Login -->

  <http auto-config="true" use-expressions="true" pattern="/user/*" >
<intercept-url pattern="/index.jsp" access="permitAll"/>
<intercept-url pattern="/user/showLoginPage.action" access="permitAll"/>
<intercept-url pattern="/user/showFirstPage" access="hasRole('ROLE_USER') or hasRole('ROLE_VISIT')"/>
<intercept-url pattern="/user/showSecondUserPage" access="hasRole('ROLE_USER')"/>
<intercept-url pattern="/user/showThirdUserPage" access="hasRole('ROLE_VISIT')"/>
<intercept-url pattern="/user/showFirstPage" access="hasRole('ROLE_USER') or hasRole('ROLE_VISIT')"/>
<form-login login-page="/user/showLoginPage.action" />
<logout invalidate-session="true"
        logout-success-url="/"
        logout-url="/user/j_spring_security_logout"/>
<access-denied-handler ref="myAccessDeniedHandler" />

  <custom-filter before="FORM_LOGIN_FILTER" ref="myApplicationFilter"/>
  </http>

    <beans:bean id="myAccessDeniedHandler" class="code.security.MyAccessDeniedHandler" />

    <beans:bean id="myApplicationFilter" class="code.security.MyApplicationFilter">
        <beans:property name="authenticationManager" ref="authenticationManager"/>
        <beans:property name="authenticationFailureHandler" ref="failureHandler"/>
        <beans:property name="authenticationSuccessHandler" ref="successHandler"/>
    </beans:bean>

    <beans:bean id="successHandler"
  class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
      <beans:property name="defaultTargetUrl" value="/user/showFirstPage">   </beans:property>
    </beans:bean>

     <beans:bean id="failureHandler"
  class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
      <beans:property name="defaultFailureUrl" value="/user/showLoginPage.action?login_error=1"/>
    </beans:bean>

    <beans:bean id= "myUserDetailServiceImpl" class="code.security.MyUserDetailServiceImpl">
    </beans:bean>

     <beans:bean id="myAuthenticationProvider" class="code.security.MyAuthenticationProvider">
        <beans:property name="userDetailsService" ref="myUserDetailServiceImpl"/>

    </beans:bean>

 <!-- User Login Ends -->

 <!-- Admin Login -->

    <http auto-config="true" use-expressions="true" pattern="/admin/*" >
    <intercept-url pattern="/index.jsp" access="permitAll"/>
    <intercept-url pattern="/admin/showSecondLogin" access="permitAll"/>
    <intercept-url pattern="/admin/*" access="hasRole('ROLE_ADMIN')"/>
    <form-login login-page="/admin/showSecondLogin"/>
    <logout invalidate-session="true"
        logout-success-url="/"
        logout-url="/admin/j_spring_security_logout"/>

    <access-denied-handler ref="myAccessDeniedHandlerForAdmin" />
    <custom-filter before="FORM_LOGIN_FILTER" ref="myApplicationFilterForAdmin"/> 
</http>

<beans:bean id="myAccessDeniedHandlerForAdmin" class="code.security.admin.MyAccessDeniedHandlerForAdmin" />

  <beans:bean id="myApplicationFilterForAdmin" class="code.security.admin.MyApplicationFilterForAdmin">
        <beans:property name="authenticationManager" ref="authenticationManager"/>
        <beans:property name="authenticationFailureHandler" ref="failureHandlerForAdmin"/>
        <beans:property name="authenticationSuccessHandler" ref="successHandlerForAdmin"/>
   </beans:bean>

    <beans:bean id="successHandlerForAdmin"
  class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
    </beans:bean>

    <beans:bean id="failureHandlerForAdmin"
  class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
       <beans:property name="defaultFailureUrl" value="/admin/showSecondLogin?login_error=1"/>
     </beans:bean>

        <authentication-manager alias="authenticationManager">
    <authentication-provider ref="myAuthenticationProviderForAdmin" />
    <authentication-provider ref="myAuthenticationProvider" />
</authentication-manager>

<beans:bean id="myAuthenticationProviderForAdmin" class="code.security.admin.MyAuthenticationProviderForAdmin">
    <beans:property name="userDetailsService" ref="userDetailsServiceForAdmin"/>
</beans:bean>

<beans:bean id= "userDetailsServiceForAdmin" class="code.security.admin.MyUserDetailsServiceForAdminImpl">
</beans:bean>

 <!-- Admin Login Ends -->

<beans:bean id="messageSource"
    class="org.springframework.context.support.ResourceBundleMessageSource">
    <beans:property name="basenames">
        <beans:list>
            <beans:value>code/security/SecurityMessages</beans:value>
        </beans:list>
    </beans:property>            
</beans:bean>

到目前为止,您可以看到,我提到的 url-pattern 是硬编码的。我想知道是否有一种方法可以动态创建新的角色和权限,而不是硬编码。

比如创建新的角色和权限并将它们保存到数据库中,然后从数据库中访问。我在网上搜索过,但我无法找到如何在代码中添加新条目。

【问题讨论】:

您的意思是要动态更改 url 的访问限制吗? @Ralph 是的。我希望能够创建新角色,然后允许他们使用一些 url 并设置限制。 你最后用了什么? 你可以在这里找到源代码github.com/springinpractice 【参考方案1】:

所以至少有两个问题:

如何使授予的权限/特权/角色动态化? 如何使 URL 的访问限制动态化?

1) 如何使授予的权限/特权/角色动态化?

我不会详细回答这个问题,因为我相信这个主题已经被讨论得足够频繁了。

最简单的方法是将完整的用户信息(登录名、密码和角色)存储在数据库中(3 个表:用户、角色、用户2角色)并使用JdbcDetailService。您可以在 xml 配置中很好地配置两个 SQL 语句(用于身份验证和授予角色)。

但随后用户需要注销并登录才能获得这些新角色。如果这是不可接受的,您还必须操纵当前登录用户的角色。它们存储在用户会话中。我想最简单的方法是在 spring 安全过滤器链中添加一个过滤器,如果需要更改每个请求,它会更新角色。

2) 如何使 URL 的访问限制动态化?

这里有两种方法:

侵入FilterSecurityInterceptor 并更新securityMetadataSource,所需的角色应该存储在那里。至少你必须操作DefaultFilterInvocationSecurityMetadataSource#lookupAttributes(String url, String method)方法的输出 另一种方法是对access 属性使用其他表达式,而不是access="hasRole('ROLE_USER')"。示例:access="isAllowdForUserPages1To3"。当然,您必须创建该方法。这称为“自定义 SpEL 表达式处理程序”(如果您有 Spring Security 3 Book,它在第 210 页左右。希望他们有章节编号!)。所以你现在需要做的就是继承WebSecurityExpressionRoot 并引入一个新方法isAllowdForUserPages1To3。然后你需要继承DefaultWebSecurityExpressionHandler并修改createEvaluationContext方法,使其第一个请求StandartEvaluationContext调用super(你需要将结果转换为StandartEvaluationContext)。然后,使用新的CustomWebSecurityExpressionRoot 实现替换StandartEvaluationContext 中的rootObject。这是最难的部分!然后,您需要用新的子类DefaultWebSecurityExpressionHandler 替换xml 配置中expressionVoter (WebExpressionVoter) 的expressionHandler 属性。 (这很糟糕,因为您首先需要显式编写大量安全配置,因为您无法直接从安全命名空间访问它们。)

【讨论】:

非常感谢您为这件事付出时间和努力。我真的很感激。 你能告诉我这个东西在网上的任何工作例子吗?因为我找不到合适的代码。 @Gurparsad Singh:抱歉,不,我已经研究过我提到的书,并为 methodExpressions 构建了类似的东西——但它是不同的,不是公开的。 (我猜你是在问第 2 点)。 @Dark Drake:请参阅 ***.com/a/12372555/280244 以获取自定义 Web 表达式的示例。【参考方案2】:

我想补充 Ralph 关于创建自定义 SpEL 表达式的回复。他的解释非常有助于我尝试找到正确的方法来做到这一点,但我认为它们需要扩展。

这是一种创建自定义 SpEL 表达式的方法:

1) 创建 WebSecurityExpressionRoot 类的自定义子类。在这个子类中创建一个您将在表达式中使用的新方法。例如:

public class CustomWebSecurityExpressionRoot extends WebSecurityExpressionRoot 

    public CustomWebSecurityExpressionRoot(Authentication a, FilterInvocation fi) 
        super(a, fi);
    

    public boolean yourCustomMethod() 
        boolean calculatedValue = ...;

        return calculatedValue;

    

2) 创建 DefaultWebSecurityExpressionHandler 类的自定义子类并覆盖方法 createSecurityExpressionRoot(Authentication authentication, FilterInvocation fi) (不是 createEvaluationContext(...)) 以返回您的 CustomWebSecurityExpressionRoot 实例。例如:

@Component(value="customExpressionHandler")
public class CustomWebSecurityExpressionHandler extends DefaultWebSecurityExpressionHandler 

    @Override
    protected SecurityExpressionRoot createSecurityExpressionRoot(
            Authentication authentication, FilterInvocation fi) 

        WebSecurityExpressionRoot expressionRoot = new CustomWebSecurityExpressionRoot(authentication, fi);

        return expressionRoot;

3) 在 spring-security.xml 中定义对表达式处理程序 bean 的引用

<security:http access-denied-page="/error403.jsp" use-expressions="true" auto-config="false">
    ...

    <security:expression-handler ref="customExpressionHandler"/>
</security:http>

在此之后,您可以使用自己的自定义表达式代替标准表达式:

<security:authorize access="yourCustomMethod()">

【讨论】:

你能给 【参考方案3】:

这个问题有一个非常直接的答案。我想知道为什么您还没有得到答案。 至少有两点应该清除:首先,您应该知道,当您使用命名空间时,会自动为您编写的每个 URL 添加一些过滤器。其次,你还应该知道每个过滤器的作用。 回到您的问题: 由于您希望动态配置拦截 URL,因此您必须删除这些命名空间,并用这些过滤器替换它们:

<bean id="springSecurityFilterChain" class="org.springframework.security.web.FilterChainProxy">
    <sec:filter-chain-map path-type="ant">
        <sec:filter-chain pattern="/css/**" filters="none" />
        <sec:filter-chain pattern="/images/**" filters="none" />
        <sec:filter-chain pattern="/login.jsp*" filters="none" />
        <sec:filter-chain pattern="/user/showLoginPage.action" filters="none" />
        <sec:filter-chain pattern="/**"
            filters="
        securityContextPersistenceFilter,
        logoutFilter,
        authenticationProcessingFilter,
        exceptionTranslationFilter,
        filterSecurityInterceptor" />
    </sec:filter-chain-map>
</bean>

然后你必须将你自己的 SecurityMetadaSource 注入到 FilterSecurityInterceptor 中。请参阅以下内容:

<bean id="filterSecurityInterceptor"
    class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
    <property name="authenticationManager" ref="authenticationManager" />
    <property name="accessDecisionManager" ref="accessDecisionManager" />
    <property name="securityMetadataSource" ref="myFilterInvocationSecurityMetadataSource" />
</bean>

<bean id="myFilterInvocationSecurityMetadataSource" class="myPackage.MyFilterSecurityMetadataSource">
</bean>

但在此之前,您必须先自定义“MyFilterSecurityMetadataSource”。 此类必须实现“DefaultFilterInvocationSecurityMetadataSource”。 由于您希望在数据库中拥有所有角色和 URL,因此您必须自定义其 getAttributes 现在看下面的实现示例:

public class MyFilterSecurityMetadataSource implements FilterInvocationSecurityMetadataSource 


    public List<ConfigAttribute> getAttributes(Object object) 
        FilterInvocation fi = (FilterInvocation) object;
        String url = fi.getRequestUrl();
        List<ConfigAttribute> attributes = new ArrayList<ConfigAttribute>();

        attributes = getAttributesByURL(url); //Here Goes Code

        return attributes;
    

    public Collection<ConfigAttribute> getAllConfigAttributes() 
        return null;
    

    public boolean supports(Class<?> clazz) 
        return FilterInvocation.class.isAssignableFrom(clazz);
    

你看到“这里是你的代码”评论吗?您必须自己实现该方法。 我自己有一个名为 URL_ACCESS 的表,其中包含 URL 及其相应的角色。收到用户的 URL 后,我查看该表并返回其相关角色。 由于我正在研究这个主题,您可以提出任何问题......我会一直回答。

【讨论】:

【参考方案4】:

您可以使用Voter 动态限制访问。另见Get Spring Security intercept urls from database or properties

【讨论】:

【参考方案5】:
    创建您的模型(用户、角色、权限)以及为给定用户检索权限的方法; 定义您自己的 org.springframework.security.authentication.ProviderManager 并将其配置为(设置其提供程序)为自定义 org.springframework.security.authentication.AuthenticationProvider;最后一个应该在其身份验证方法上返回一个身份验证,它应该使用 GrantedAuthority 设置,在您的情况下,是给定用户的所有权限。

那篇文章中的技巧是将角色分配给用户,但是,在 Authentication.authorities 对象中为这些角色设置权限。

为此,我建议您阅读 API,看看您是否可以扩展一些基本的 ProviderManager 和 AuthenticationProvider 而不是实现所有内容。我已经通过设置自定义 LdapAuthoritiesPopulator 的 LdapAuthenticationProvider 来完成此操作,这将为用户检索正确的角色。

【讨论】:

以上是关于在 Spring Security 3 中动态创建新角色和权限的主要内容,如果未能解决你的问题,请参考以下文章

spring security3 动态从数据库中读取权限信息<sec:authorize>标签 url属性不起作用

248.Spring Boot+Spring Security:基于URL动态权限:准备工作

222.Spring Boot+Spring Security:动态加载角色

如何在 Spring Security 中动态切换应用程序上下文?

spring security动态管理资源结合自定义登录页面

249.Spring Boot+Spring Security:基于URL动态权限:扩展access()的SpEL表达式