如何允许用户使用 Spring Security 进行覆盖?

发布于 2024-12-06 11:20:28 字数 4140 浏览 1 评论 0 原文

在我的 Spring MVC Web 应用程序中,某些区域只能由具有足够权限的用户访问。我需要能够允许用户以不同用户身份登录才能使用这些页面(有点像覆盖),而不仅仅是显示“访问被拒绝”消息。

我该如何使用 Spring Security 来做到这一点?

这是我想要的流程,更详细一些:

  1. 用户 A 从外部应用程序进入页面 X 并通过标头进行身份验证
  2. 用户 A 没有使用页面 X 的权限,因此被带到登录屏幕一条消息,表明他们必须以具有足够权限的用户身份登录才能使用此页面
  3. 用户 B 登录,并具有足够的权限,并被带到页面 X。

注意:页面 X 有一个又大又长的查询字符串,需要保存下来。

我该如何使用 Spring Security 来做到这一点?


这是我的 spring security 配置文件:

<?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"
    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">

    <debug />

    <global-method-security pre-post-annotations="enabled">
        <!-- AspectJ pointcut expression that locates our "post" method and applies 
            security that way <protect-pointcut expression="execution(* bigbank.*Service.post*(..))" 
            access="ROLE_TELLER"/> -->
    </global-method-security>

    <!-- Allow anyone to get the static resources and the login page by not applying the security filter chain -->
    <http pattern="/resources/**" security="none" />
    <http pattern="/css/**" security="none" />
    <http pattern="/img/**" security="none" />
    <http pattern="/js/**" security="none" />

    <!-- Lock everything down -->
    <http 
        auto-config="true"
        use-expressions="true" 
        disable-url-rewriting="true">

        <!-- Define the URL access rules -->
        <intercept-url pattern="/login" access="permitAll" />
        <intercept-url pattern="/about/**" access="permitAll and !hasRole('blocked')" />
        <intercept-url pattern="/users/**" access="hasRole('user')" />
        <intercept-url pattern="/reviews/new**" access="hasRole('reviewer')" />
        <intercept-url pattern="/**" access="hasRole('user')" />

        <form-login 
            login-page="/login" />

        <logout logout-url="/logout" /> 

        <access-denied-handler error-page="/login?reason=accessDenied"/>

        <!-- Limit the number of sessions a user can have to only 1 -->
        <session-management>
            <concurrency-control max-sessions="1" />
        </session-management>
    </http>

    <authentication-manager>
        <authentication-provider ref="adAuthenticationProvider" />
        <authentication-provider>
            <user-service>
                <user name="superadmin" password="superadminpassword" authorities="user" />
            </user-service>
        </authentication-provider>
    </authentication-manager>

    <beans:bean id="adAuthenticationProvider" class="[REDACTED Package].NestedGroupActiveDirectoryLdapAuthenticationProvider">
        <beans:constructor-arg value="[REDACTED FQDN]" />
        <beans:constructor-arg value="[REDACTED LDAP URL]" />
        <beans:property name="convertSubErrorCodesToExceptions" value="true" />
        <beans:property name="[REDACTED Group Sub-Tree DN]" />
        <beans:property name="userDetailsContextMapper" ref="peerReviewLdapUserDetailsMapper" />
    </beans:bean>

    <beans:bean id="peerReviewLdapUserDetailsMapper" class="[REDACTED Package].PeerReviewLdapUserDetailsMapper">
        <beans:constructor-arg ref="UserDAO" />
    </beans:bean>

</beans:beans>

我使用的是 Spring Security 3.1 Active Directory 连接功能的稍微修改版本。这些修改只是加载用户的所有组,包括通过组嵌套到达的组,而不仅仅是用户直接所属的组。我还使用一个自定义用户对象,其中嵌入了我的应用程序的用户对象,以及一个自定义 LDAP 映射器,该映射器执行正常的 LDAP 映射,然后添加我的用户。

有一种尚未实现的特殊身份验证方案,其中用户根据从外部应用程序(或通过 Kerberos)以单点登录方式传递的用户名进行身份验证。

In my Spring MVC web application, there are certain areas accessible only to users with sufficient privileges. Rather than just have a "access denied" message, I need to be able to allow users to log in as a different user in order to use these pages (sort of like an override).

How can I do this with Spring Security?

Here's the flow I am looking to have, with a bit more detail:

  1. User A comes in to page X from external application and is authenticated via headers
  2. User A does not have permission to use page X, and so is taken to the login screen with a message indicating that they must log in as a user with sufficient privilages to use this page
  3. User B logs in, and has sufficient privilages, and is taken to page X.

Note: Page X has a big, long query string that needs to be preserved.

How can I do this with Spring Security?


Here's my spring security config file:

<?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"
    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">

    <debug />

    <global-method-security pre-post-annotations="enabled">
        <!-- AspectJ pointcut expression that locates our "post" method and applies 
            security that way <protect-pointcut expression="execution(* bigbank.*Service.post*(..))" 
            access="ROLE_TELLER"/> -->
    </global-method-security>

    <!-- Allow anyone to get the static resources and the login page by not applying the security filter chain -->
    <http pattern="/resources/**" security="none" />
    <http pattern="/css/**" security="none" />
    <http pattern="/img/**" security="none" />
    <http pattern="/js/**" security="none" />

    <!-- Lock everything down -->
    <http 
        auto-config="true"
        use-expressions="true" 
        disable-url-rewriting="true">

        <!-- Define the URL access rules -->
        <intercept-url pattern="/login" access="permitAll" />
        <intercept-url pattern="/about/**" access="permitAll and !hasRole('blocked')" />
        <intercept-url pattern="/users/**" access="hasRole('user')" />
        <intercept-url pattern="/reviews/new**" access="hasRole('reviewer')" />
        <intercept-url pattern="/**" access="hasRole('user')" />

        <form-login 
            login-page="/login" />

        <logout logout-url="/logout" /> 

        <access-denied-handler error-page="/login?reason=accessDenied"/>

        <!-- Limit the number of sessions a user can have to only 1 -->
        <session-management>
            <concurrency-control max-sessions="1" />
        </session-management>
    </http>

    <authentication-manager>
        <authentication-provider ref="adAuthenticationProvider" />
        <authentication-provider>
            <user-service>
                <user name="superadmin" password="superadminpassword" authorities="user" />
            </user-service>
        </authentication-provider>
    </authentication-manager>

    <beans:bean id="adAuthenticationProvider" class="[REDACTED Package].NestedGroupActiveDirectoryLdapAuthenticationProvider">
        <beans:constructor-arg value="[REDACTED FQDN]" />
        <beans:constructor-arg value="[REDACTED LDAP URL]" />
        <beans:property name="convertSubErrorCodesToExceptions" value="true" />
        <beans:property name="[REDACTED Group Sub-Tree DN]" />
        <beans:property name="userDetailsContextMapper" ref="peerReviewLdapUserDetailsMapper" />
    </beans:bean>

    <beans:bean id="peerReviewLdapUserDetailsMapper" class="[REDACTED Package].PeerReviewLdapUserDetailsMapper">
        <beans:constructor-arg ref="UserDAO" />
    </beans:bean>

</beans:beans>

I'm using a slightly modified version of the Spring Security 3.1 Active Directory connection capabilities. The modifications simply load all of a user's groups, including those reached by group nesting, rather than only the ones the user is directly a member of. I'm also using a custom user object that has my application's User object embedded in it, and a custom LDAP mapper that does the normal LDAP mapping, and then adds in my user.

There is a special authentication scenario that has not been implemented yet where the user is authenticated based on a username passed from an external application (or via Kerberos) in a Single-Sign-On fashion.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

蓝海 2024-12-13 11:20:28

如何检查角色?

如果您在安全上下文中像这样定义它们:

<intercept-url pattern="/adminStuff.html**" access="hasRole('ROLE_ADMIN')" />

您可以在您的 defaultFailureUrl。 x/apidocs/org/springframework/security/web/authentication/SimpleUrlAuthenticationFailureHandler.html" rel="nofollow">SimpleUrlAuthenticationFailureHandler 并且当用户使用如果权限较低,则尝试访问安全 URL,FaliureHandler 应将您重定向到 defaultFailureUrl,这可能是您的登录页面。

您可以在 FORM_LOGIN_FILTER 位置过滤

<bean id="myFaliureHandler" 
    class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
    <property name="defaultFailureUrl" value="http://yourdomain.com/your-login.html"/>
</bean>

<bean id="myFilter"
   class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
   <property name="authenticationFailureHandler" ref="myFaliureHandler"/>
</bean>    

<http>
  <custom-filter position="FORM_LOGIN_FILTER" ref="myFilter" />
</http>

在评论中回答1)。

考虑到您的命名空间配置,这比我想象的要多一些工作。

您需要做的是删除 定义,并添加一个“自定义”UsernamePasswordAuthenticationFilter (这是处理 元素)。

您还需要删除

所以你的配置看起来像这样:

<bean id="myFaliureHandler" 
    class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
    <property name="defaultFailureUrl" value="http://yourdomain.com/your-login.html"/>
</bean>

<bean id="myFilter"
   class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
   <property name="authenticationFailureHandler" ref="myFaliureHandler"/>
   <!-- there are more required properties, but you can read about them in the docs -->
</bean>

<bean id="loginUrlAuthenticationEntryPoint"
   class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
   <property name="loginFormUrl" value="/login"/>
</bean>

<http entry-point-ref="authenticationEntryPoint" auto-config="false">

  <!-- your other http config goes here, just omit the form-login element and the access denied handler -->

  <custom-filter position="FORM_LOGIN_FILTER" ref="myFilter" />
</http>

通常还可以看看关于自定义过滤器的 Spring 文档(如果您还没有)。目前,我们在我当前的公司中使用此配置,如果用户在页面上没有所需的权限,则强制用户重新登录。

How do you check for roles ?

If you define them in your security context like this:

<intercept-url pattern="/adminStuff.html**" access="hasRole('ROLE_ADMIN')" />

You can set the defaultFailureUrl in your SimpleUrlAuthenticationFailureHandler and when a user with lesser privileges tries to access a secured URL the FaliureHandler should redirect you to the defaultFailureUrl which could be your login page.

You can inject a FaliureHandler in the filter at the FORM_LOGIN_FILTER position.

<bean id="myFaliureHandler" 
    class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
    <property name="defaultFailureUrl" value="http://yourdomain.com/your-login.html"/>
</bean>

<bean id="myFilter"
   class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
   <property name="authenticationFailureHandler" ref="myFaliureHandler"/>
</bean>    

<http>
  <custom-filter position="FORM_LOGIN_FILTER" ref="myFilter" />
</http>

Answering 1) in the comment.

This would be a little more work than I thought given your namespace configuration.

What you need to do is remove the <form-login> definition and instead of it add a 'custom' UsernamePasswordAuthenticationFilter (this is the filter that handles the <form-login> element).

You also need to remove the <access-denied-handler>.

So your configuration would look something like:

<bean id="myFaliureHandler" 
    class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
    <property name="defaultFailureUrl" value="http://yourdomain.com/your-login.html"/>
</bean>

<bean id="myFilter"
   class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
   <property name="authenticationFailureHandler" ref="myFaliureHandler"/>
   <!-- there are more required properties, but you can read about them in the docs -->
</bean>

<bean id="loginUrlAuthenticationEntryPoint"
   class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
   <property name="loginFormUrl" value="/login"/>
</bean>

<http entry-point-ref="authenticationEntryPoint" auto-config="false">

  <!-- your other http config goes here, just omit the form-login element and the access denied handler -->

  <custom-filter position="FORM_LOGIN_FILTER" ref="myFilter" />
</http>

Generally also have a look at the spring docs on custom filters, if you haven't already. We currently use this config in my current company forcing users to relogin if the don't have required privileges on a page.

屌丝范 2024-12-13 11:20:28

解决方案 1

注册应用程序范围 ExceptionResolver 使用您喜欢的任何方式。对于前。

public class MyApplicationErrorResolver extends SimpleMappingExceptionResolver {

    @Autowired
    private List<LogoutHandler> logoutHandlers;

    @Override
    protected ModelAndView doResolveException(HttpServletRequest request,
        HttpServletResponse response, Object handler, Exception ex) {

        if(ex instanceof AccessDeniedException) {
            for(LogoutHandler lh : logoutHandlers) {
                lh.logout(request, response, SecurityContextHolder.getContext().getAuthentication());
            }
            // Not present as a bean. So create it manually.
            SecurityContextLogoutHandler logoutHandler = new SecurityContextLogoutHandler();
            logoutHandler.setInvalidateHttpSession(true);
            logoutHandler.logout(request, response, SecurityContextHolder.getContext().getAuthentication());

            return new ModelAndView(new RedirectView(request.getRequestURL().toString()));
        }

        return super.doResolveException(request, response, handler, ex);
    }
}

将其注册为 bean:(

<bean class="package.path.MyApplicationErrorResolver" />

这就是注册所需的全部内容)。这将适用于您的配置。但是您可能需要从配置中删除 元素。

解决方案 2

另一种方法是使用 AccessDeniedHandler。例如:

public class MyAccessDeniedExceptionHandler implements AccessDeniedHandler {

    @Autowired
    private List<LogoutHandler> logoutHandlers;

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
        for(LogoutHandler lh : logoutHandlers) {
            lh.logout(request, response, SecurityContextHolder.getContext().getAuthentication());
        }

        SecurityContextLogoutHandler logoutHandler = new SecurityContextLogoutHandler();
        logoutHandler.setInvalidateHttpSession(true);
        logoutHandler.logout(request, response, SecurityContextHolder.getContext().getAuthentication());

        response.sendRedirect(request.getRequestURL().toString());
    }

}

将其注册为 bean:

<bean id="accesssDeniedHandler" class="package.path.MyAccessDeniedExceptionHandler" />

并在配置中指定它:

<access-denied-handler ref="accesssDeniedHandler" />

Solution 1

Register an application wide ExeptionResolver using anyway you like. For ex.

public class MyApplicationErrorResolver extends SimpleMappingExceptionResolver {

    @Autowired
    private List<LogoutHandler> logoutHandlers;

    @Override
    protected ModelAndView doResolveException(HttpServletRequest request,
        HttpServletResponse response, Object handler, Exception ex) {

        if(ex instanceof AccessDeniedException) {
            for(LogoutHandler lh : logoutHandlers) {
                lh.logout(request, response, SecurityContextHolder.getContext().getAuthentication());
            }
            // Not present as a bean. So create it manually.
            SecurityContextLogoutHandler logoutHandler = new SecurityContextLogoutHandler();
            logoutHandler.setInvalidateHttpSession(true);
            logoutHandler.logout(request, response, SecurityContextHolder.getContext().getAuthentication());

            return new ModelAndView(new RedirectView(request.getRequestURL().toString()));
        }

        return super.doResolveException(request, response, handler, ex);
    }
}

register it as a bean:

<bean class="package.path.MyApplicationErrorResolver" />

(that's all you need to register it). This will work for your configuration. Bu you will probably need to remove the <access-denied-handler> element from the config.

Solution 2

Another way is to use an AccessDeniedHandler. For ex:

public class MyAccessDeniedExceptionHandler implements AccessDeniedHandler {

    @Autowired
    private List<LogoutHandler> logoutHandlers;

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
        for(LogoutHandler lh : logoutHandlers) {
            lh.logout(request, response, SecurityContextHolder.getContext().getAuthentication());
        }

        SecurityContextLogoutHandler logoutHandler = new SecurityContextLogoutHandler();
        logoutHandler.setInvalidateHttpSession(true);
        logoutHandler.logout(request, response, SecurityContextHolder.getContext().getAuthentication());

        response.sendRedirect(request.getRequestURL().toString());
    }

}

register it as a bean:

<bean id="accesssDeniedHandler" class="package.path.MyAccessDeniedExceptionHandler" />

and specify it in your config:

<access-denied-handler ref="accesssDeniedHandler" />
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文