AngularJS Web Application with Spring Security

2020-03-03 08:34发布

问题:

I am working on implementing Spring Security in an AngularJS application. I am relatively new to both technologies, and I have found several very helpful sites with tutorials and examples of how to implement AngularJS and Spring Security.

My problem currently lies in restricting URL paths to certain users. IT sounds like a simple problem, but I have drowned myself with documentation trying to figure out a problem that must have been solved previously.

In AngularJS, there is a hash mark in the URL when navigating to different URLs and that seems to cause a problem with Spring. There is no error thrown, but the resource is not restricted. My code is below:

web.xml

<?xml version="1.0" encoding="UTF-8"?>

<web-app xmlns="http://java.sun.com/xml/ns/javaee" 
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
             http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
        /WEB-INF/paperwebapp-servlet.xml
    </param-value>
</context-param>

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<servlet>
    <servlet-name>webapp</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>*</param-value>
        </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>webapp</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

<filter>
    <filter-name>characterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
    <init-param>
        <param-name>forceEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>

<filter-mapping>
    <filter-name>characterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

<!-- Apply Spring Security Filter to all Requests -->
<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

app-servlet.xml

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

<context:property-placeholder location="file:${catalina_home}/conf/application.properties" />

<mvc:view-controller path="/" view-name="/resources/index.html"/>
<mvc:resources mapping="/resources/**" location="/resources/" />

<import resource="spring-security.xml" />

</beans>

spring-security.xml

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

<context:property-placeholder location="file:${catalina_home}/conf/application.properties" />

<sec:http auto-config='true'>
    <sec:intercept-url pattern="/access/login.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
    <sec:intercept-url pattern="/*" access="ROLE_USER" />
    <sec:intercept-url pattern="/#/inventory" access="ROLE_ADMIN" />

    <sec:form-login login-page="/access/login.jsp" default-target-url="/#/splash"
        always-use-default-target="true" />
</sec:http>

<sec:authentication-manager alias="authenticationManager">
    <sec:authentication-provider>
        <sec:user-service>
            <sec:user name="admin" password="admin" authorities="ROLE_USER, ROLE_ADMIN" />
            <sec:user name="user" password="user" authorities="ROLE_USER" />
        </sec:user-service>
    </sec:authentication-provider>
</sec:authentication-manager>
</beans>

When the application is deployed, I can access the URL (localhost:8080/app) and I am greeted with a login page as expected. Once I am authenticated, i am brought to the splash screen (/#/splash) also as expected. However, if I log in using the "user" credentials, I should be restricted from the /inventory path. No matter what I try (/#/inventory, /inventory, #/inventory, etc) I cannot get the resource to be restricted. I have tested this configuration out on an application that accesses HTML pages directly from the application directory and it seems to work fine, thus I am convinced that it has something to do with the AngularJS controller routing the requests and using that hash mark.

Another point of interest I discovered in researching this was that because we are using templates from multiple sources to compile each page we cannot use the $locationProvider to set HTML5 mode without breaking the application.

If anyone has any insight into this problem, it would be greatly appreciated. I am sure this has been somewhere, but for the life of me I could not find anything. Thank you!

回答1:

You seem to be confusing Angular's client-side URL handling (based on the hashes, like '/#/splash') with Spring MVC's (and therefore Spring Security's) server-side URL handling.

Remember that Spring Security secures access to URLs on the server side, and that Angular is a single-page, client-side library.

When you first access your Angular page, you are getting it from the server, and Spring Security can restrict access depending on the login.

If you're still in your single-page Angular environment on the client, and navigate via Angular to "page" with a hash in the URL (See the AngularJS $location guide about hashbang URLs), you are not making a server request. You are requesting Angular to render a different template or state. This is client-side behavior, so Spring Security is not involved. You make a server request when you access a template HTML file (which Spring may return statically without authentication) or if you have set up a REST api to get data from your server (typically returning JSON formatted data for your application to use).

I believe that to make this work, the data for your protected resource ("/inventory") would have to not be included in the main application, requiring a separate server-side resource. This could include either the HTML template for the page and/or the data. Your AngularJS application should be able to recognize that this resource is not available and display something to the user to communicate the lack of authorization.