【问题标题】:Spring security using model properties to apply rolesSpring Security 使用模型属性来应用角色
【发布时间】:2011-06-17 06:11:14
【问题描述】:

我有一个 Spring MVC 应用程序,我希望将 Spring Security 与 (Spring 3.0.x) 集成。

web.xml 包含:

<context-param>
    <description>Context Configuration locations for Spring XML files</description>
    <param-name>contextConfigLocation</param-name>
    <param-value>
        classpath*:spring/spring-model.xml
        classpath*:spring/spring-compiler.xml
        classpath*:spring/spring-ui.xml
        classpath*:spring/spring-security.xml
    </param-value>
</context-param>
<listener>
    <description><![CDATA[
        Loads the root application context of this web app at startup, use 
        contextConfigLocation paramters defined above or by default use "/WEB-INF/applicationContext.xml".
        - Note that you need to fall back to Spring's ContextLoaderServlet for
        - J2EE servers that do not follow the Servlet 2.4 initialization order.

        Use WebApplicationContextUtils.getWebApplicationContext(servletContext) to access it anywhere in the web application, outside of the framework.

        The root context is the parent of all servlet-specific contexts.
        This means that its beans are automatically available in these child contexts,
        both for getBean(name) calls and (external) bean references.
    ]]></description>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
    <description>Configuration for the Spring MVC webapp servlet</description>
    <servlet-name>springmvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath*:spring/spring-mvc.xml</param-value>
    </init-param>
</servlet>
<servlet-mapping>
    <servlet-name>springmvc</servlet-name>
    <url-pattern>/app/*</url-pattern>
</servlet-mapping>

我想添加基于角色的安全性,以便用户无法访问网站的某些部分。

例如用户应该具有CRICKET_USER 角色才能访问http://example.com/sports/cricketFOOTBALL_USER 角色才能访问http://example.com/sports/football

应用程序中的 URI 保留此层次结构,因此可能存在 http://example.com/sports/football/leagues/premiership 等资源,这同样需要用户具有角色 FOOTBALL_USER

我有一个这样的控制器:

@Controller("sportsController")
@RequestMapping("/sports/{sportName}")
public class SportsController {

    @RequestMapping("")
    public String index(@PathVariable("sportName") Sport sport, Model model) {
        model.addAttribute("sport", sport);
        return "sports/index";
    }

}

我一直在尝试使用最惯用、最明显的方式来满足这个要求,但我不确定我是否找到了它。我尝试了 4 种不同的方法。

@PreAuthorize 注释

我尝试在该控制器上的每个 @RequestMapping 方法上使用 @PreAuthorize("hasRole(#sportName.toUpperCase() + '_USER')")(以及其他处理 URI 请求的控制器进一步向下层级。我无法使其正常工作;没有错误,但它没有似乎什么也没做。

坏点:

  • 不起作用?
  • @Controller 上的方法级别注释,而不是类级别。那不是很干。此外,如果添加了更多功能并且有人忘记将注释添加到新代码中,则可能会留下安全漏洞。
  • 我无法为它编写测试。

Spring Security 链中的拦截-url

<http use-expressions="true">

    <!--  note that the order of these filters are significant -->
    <intercept-url pattern="/app/sports/**" access="hasRole(#sportName.toUpperCase() + '_USER')" />

    <form-login always-use-default-target="false"
        authentication-failure-url="/login/" default-target-url="/"
        login-page="/login/" login-processing-url="/app/logincheck"/>
    <!-- This action catch the error message and make it available to the view -->
    <anonymous/>
    <http-basic/>
    <access-denied-handler error-page="/app/login/accessdenied"/>
    <logout logout-success-url="/login/" logout-url="/app/logout"/>
</http>

感觉它应该可以工作,其他开发人员很清楚它在做什么,但我没有成功使用这种方法。我对这种方法的唯一痛点是无法编写一个测试来标记问题,如果事情发生变化。

java.lang.IllegalArgumentException: Failed to evaluate expression 'hasRole(#sportName.toUpper() + '_USER')'
at org.springframework.security.access.expression.ExpressionUtils.evaluateAsBoolean(ExpressionUtils.java:13)
at org.springframework.security.web.access.expression.WebExpressionVoter.vote(WebExpressionVoter.java:34)
...
Caused by: 
org.springframework.expression.spel.SpelEvaluationException: EL1011E:(pos 17): Method call: Attempted to call method toUpper() on null context object
at org.springframework.expression.spel.ast.MethodReference.getValueInternal(MethodReference.java:69)
at org.springframework.expression.spel.ast.CompoundExpression.getValueInternal(CompoundExpression.java:57)

Spring Security 链中的标准过滤器。

public class SportAuthorisationFilter extends GenericFilterBean {

    /**
     * {@inheritDoc}
     */
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

        HttpServletRequest httpRequest = (HttpServletRequest) request;

        String pathInfo = httpRequest.getPathInfo();

        /* This assumes that the servlet is coming off the /app/ context and sports are served off /sports/ */
        if (pathInfo.startsWith("/sports/")) {

            String sportName = httpRequest.getPathInfo().split("/")[2];

            List<String> roles = SpringSecurityContext.getRoles();

            if (!roles.contains(sportName.toUpperCase() + "_USER")) {
                throw new AccessDeniedException(SpringSecurityContext.getUsername()
                        + "is  not permitted to access sport " + sportName);
            }
        }

        chain.doFilter(request, response);
    }
}

和:

<http use-expressions="true">

    <!--  note that the order of these filters are significant -->

    <!--
      Custom filter for /app/sports/** requests. We wish to restrict access to those resources to users who have the
      {SPORTNAME}_USER role.
    -->
    <custom-filter before="FILTER_SECURITY_INTERCEPTOR" ref="sportsAuthFilter"/>
    <form-login always-use-default-target="false"
        authentication-failure-url="/login/" default-target-url="/"
        login-page="/login/" login-processing-url="/app/logincheck"/>
    <!-- This action catch the error message and make it available to the view -->
    <anonymous/>
    <http-basic/>
    <access-denied-handler error-page="/app/login/accessdenied"/>
    <logout logout-success-url="/login/" logout-url="/app/logout"/>
</http>

<beans:bean id="sportsAuthFilter" class="com.example.web.controller.security.SportsAuthorisationFilter" />

加分:

  • 这行得通

坏点:

  • 没有测试。
  • 如果我们的应用程序 URI 结构发生变化,可能会很脆弱。
  • 对于下一个要更改代码的人来说并不明显。

在 @PathVariable 使用的 Formatter 实现中验证

@Component
public class SportFormatter implements DiscoverableFormatter<Sport> {

@Autowired
private SportService SportService;

public Class<Sport> getTarget() {
    return Sport.class;
}

public String print(Sport sport, Locale locale) {
    if (sport == null) {
        return "";
    }
    return sport.getName();
}

public Sport parse(String text, Locale locale) throws ParseException {
    Sport sport;

    if (text == null || text.isEmpty()) {
        return new Sport();
    }

    if (NumberUtils.isNumber(text)) {
        sport = sportService.getByPrimaryKey(new Long(text));
    } else {
        Sport example = new Sport();
        example.setName(text);
        sport = sportService.findUnique(example);
    }

    if (sport != null) {
        List<String> roles = SpringSecurityContext.getRoles();

        if (!roles.contains(sportName.toUpperCase() + "_USER")) {
            throw new AccessDeniedException(SpringSecurityContext.getUsername()
                    + "is  not permitted to access sport " + sportName);
        }      
    }

    return sport != null ? sport : new Sport();
    }
}

加分:

  • 这行得通。

坏点:

  • 这是否依赖于控制器中每个带有 @PathVariable 的 @RequestMapping 注释方法来检索 Sport 实例?
  • 没有测试。

请指出我缺少精美手册的哪一部分。

【问题讨论】:

    标签: java spring spring-mvc annotations spring-security


    【解决方案1】:

    您需要使用#sport.name.toUpper() 之类的东西而不是#sportName.toUpper(),因为@PreAuthorize 中的#... 变量指的是方法参数:

    @RequestMapping(...)
    @PreAuthorize("hasRole(#sport.name.toUpper() + '_USER')") 
    public String index(@PathVariable("sportName") Sport sport, Model model) { ... }
    

    另请参阅:

    【讨论】:

    • 我仍然缺少一些东西。我在配置中有 ,但我没有看到注释表达式被评估;特别是,当我将其设置为 denyAll() 时,我希望得到一个合适的失败。
    • @jabley:确保您在...-servlet.xml 中也有&lt;global-method-security&gt;,而不仅仅是在applicationContext.xml 中。
    • 我没有 ...-servlet.xml。附加 web.xml sn-p。我有一个 spring-security.xml 和其他各种 spring xml 文档。 spring-security.xml 文档是唯一包含 元素的文档。用 XML 编码是这样的乐趣。
    • @jabley:那我的意思是spring-mvc.xml
    • 哇。这行得通,谢谢。您能帮我理解为什么必须在一个文件中定义它而不是另一个文件吗?
    【解决方案2】:

    我也找到了解决方案,使用:

    <security:global-method-security secured-annotations="enabled" proxy-target-class="true"/>
    

    希望对大家有所帮助。

    【讨论】:

    猜你喜欢
    • 2014-05-09
    • 2011-08-11
    • 1970-01-01
    • 2016-05-19
    • 1970-01-01
    • 2015-07-29
    • 2011-07-21
    • 2020-04-11
    • 2012-12-25
    相关资源
    最近更新 更多