10.12.2012

Vaadin и Spring Security.

В предыдущей статье было показано как интегрировать Spring в Vaadin, продолжаем его улучшать добавив к нему поддержку Spring Security. Я не буду останавливаться на настройке Spring Security просто раскажу как происходит аутентификация - процедура проверки подлинности данных, т. е. проверка соответствия введённого пользователем пароля к учётной записи и паролю в файле application-security.xml. Приведу сразу листинг файла application-security.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:beans="http://www.springframework.org/schema/beans"
             xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.1.xsd">
    <http auto-config="true" use-expressions="true">
        <intercept-url pattern="/*" access="hasAnyRole('ROLE_USER','ROLE_ANONYMOUS')"/>
        <form-login login-page="/login" default-target-url="/" authentication-failure-url="/login_error"/>
        <logout invalidate-session="true"/>
    </http>
    <authentication-manager alias="authenticationManager">
        <authentication-provider>
            <user-service>
                <user authorities="ROLE_USER" name="test" password="test"/>
            </user-service>
        </authentication-provider>
    </authentication-manager>
    <global-method-security pre-post-annotations="enabled"/>
</beans:beans>
из него видно что аутентификация происходит на основе формы при помощи странички /login которая дает нам ввести пользователя и пароль, если аутентификация прошла успешно то мы переходим к основной страничке приложения / если нет то выводим страничку с ошибкой /login_error. приведу сразу листинг web.xml с настройками страниц /login - login.jsp, /login_error - login_error.jsp:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         id="WebApp_ID" version="2.5">
    <display-name>Vaadin Web Application</display-name>
    <context-param>
        <description>Vaadin production mode</description>
        <param-name>productionMode</param-name>
        <param-value>false</param-value>
    </context-param>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            /WEB-INF/applicationContext.xml
            /WEB-INF/application-security.xml
        </param-value>
    </context-param>

    <servlet>
        <servlet-name>Vaadin Application Servlet</servlet-name>
        <servlet-class>com.vit.AutowiringApplicationServlet</servlet-class>
        <init-param>
            <param-name>application</param-name>
            <param-value>com.vit.MyVaadinApplication</param-value>
        </init-param>
    </servlet>
    <servlet>
     <servlet-name>login</servlet-name>
     <jsp-file>/WEB-INF/jsp/login.jsp</jsp-file>
    </servlet>
    <servlet>
     <servlet-name>login_error</servlet-name>
     <jsp-file>/WEB-INF/jsp/login_error.jsp</jsp-file>
    </servlet>
    <servlet-mapping>
        <servlet-name>Vaadin Application Servlet</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
     <servlet-name>login</servlet-name>
     <url-pattern>/login</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
     <servlet-name>login_error</servlet-name>
     <url-pattern>/login_error</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>
    </filter>
    <filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>
            org.springframework.web.filter.DelegatingFilterProxy
        </filter-class>
    </filter>

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

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <listener>
        <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
    </listener>
    <listener>
        <listener-class>
            org.springframework.security.web.session.HttpSessionEventPublisher
        </listener-class>
    </listener>

</web-app>
страничка где вводим пользователя и пароль - листинг login.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
 <div align="center">
 Login here
 <form action= "<%= request.getContextPath() %>/j_spring_security_check" method="post">
 <table>
  <tr>
   <td>
    User
   </td>
   <td>
    <input name="j_username" value="test">
   </td>
  </tr>
  <tr>
   <td>
    Password
   </td>
   <td>
    <input type="password" name="j_password" value="test"/>
   </td>
  </tr>
  <tr>
   <td>
    <input type="submit" value="login">
   <td>
  </tr>
 </table>
 </form> 
 </div>
</body>
</html>
страничка куда попадаем если не правильно ввели данные аутентификации листинг login_error.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
 <H1 align="center">Access Denied</H1>
 <div align="left" >
        <a href="<%= request.getContextPath() %>/login" > Login </a></div>
    <div align="left" ><a href="<%= request.getContextPath() %>" > Home </a></div>
</body>
</html>
При работе с нашим приложение любому пользователю, Spring'ом присваивается роль ROLE_ANONYMOUS, если пользователь успешно прошёл аутентификацию, то ему присваивается роль ROLE_USER.
package com.vit;

public interface Roles {
    String ROLE_USER = "ROLE_USER";
    String ROLE_ANONYMOUS = "ROLE_ANONYMOUS";
}
С помощью метода hasAnyRole(ROLE_USER) мы можем узнать залогинелся ли пользователь и соответственно раздавть ему полагаемые ресурсы, данные. Сылки "Login" и "Logout" я вынеc в одельную панель HeadPanel:
package com.vit;

import com.vaadin.terminal.ExternalResource;
import com.vaadin.ui.*;
import com.vaadin.ui.themes.BaseTheme;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import java.util.Collection;
import static com.vit.Roles.ROLE_USER;
import static com.vit.Roles.ROLE_ANONYMOUS;

public class HeadPanel extends Panel {
    public HorizontalLayout hLayout;
    public HorizontalLayout cssLayout;

    public HeadPanel() {
        setImmediate(true);
        cssLayout = new HorizontalLayout();
        cssLayout.setSpacing(true);

        hLayout = new HorizontalLayout();
        hLayout.setWidth("100%");

        if (hasAnyRole(ROLE_ANONYMOUS)) {
            Button b = new Button("LOGIN");
            b.setStyleName(BaseTheme.BUTTON_LINK);
            b.setWidth("60px");
            b.addListener(new Button.ClickListener() {
                public void buttonClick(Button.ClickEvent clickEvent) {
                    final String path = getApplication().getURL().getPath();
                    getApplication().getMainWindow().open(
                            new ExternalResource(path + "login"));
                }
            }); // react to clicks
            cssLayout.addComponent(b);
            cssLayout.setComponentAlignment(b, Alignment.MIDDLE_LEFT);
        } else {
            Label userName = new Label(currentUserName());
            userName.setWidth("130px");
            cssLayout.addComponent(userName);
            cssLayout.setComponentAlignment(userName, Alignment.MIDDLE_LEFT);
        }
        if (hasAnyRole(ROLE_USER)) {
            Button logout = new Button("Loguot");
            logout.setStyleName(BaseTheme.BUTTON_LINK);
            logout.setWidth("60px");
            logout.addListener(new Button.ClickListener() {
                private static final long serialVersionUID = 1L;

                public void buttonClick(Button.ClickEvent clickEvent) {
                    LoguotWindow loguotWindow = new LoguotWindow("", "Are you sure?");
                    getApplication().getMainWindow().addWindow(loguotWindow);
                }
            });
            cssLayout.addComponent(logout);
            cssLayout.setComponentAlignment(logout, Alignment.MIDDLE_LEFT);
        }
        hLayout.addComponent(cssLayout);
        hLayout.setComponentAlignment(cssLayout, Alignment.MIDDLE_RIGHT);
        addComponent(hLayout);
    }

    public boolean hasAnyRole(String... roles) {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        Collection authorities = (Collection) authentication.getAuthorities();
        for (GrantedAuthority authority : authorities) {
            for (String role : roles) {
                if (role.equals(authority.getAuthority())) {
                    return true;
                }
            }
        }
        return false;
    }

    public String currentUserName() {
        return SecurityContextHolder.getContext().getAuthentication().getName();
    }
}
При завершении работы пользователя в Vaadin мы должны очистить асоциированные данные с ним посредством вызова метода getApplication().close(); и предупредить Spring что мы сделали "Logout".
package com.vit;

import com.vaadin.ui.*;

public class LoguotWindow extends Window {

    public VerticalLayout vLayout;
    public Label qLabel;
    public HorizontalLayout hLayout;
    public Button close;
    public Button closeWindow;

    public LoguotWindow(String caption, String qCaption) {
        super(caption);
        center();
        setImmediate(true);
        setClosable(true);
        setModal(true);
        setWidth("200px");
        setHeight("200px");
        setScrollable(false);
        vLayout = new VerticalLayout();
        vLayout.setSpacing(true);
        vLayout.setSizeFull();
        qLabel = new Label(qCaption);
        vLayout.addComponent(qLabel);
        hLayout = new HorizontalLayout();
        hLayout.setSpacing(true);
        close = new Button("YES", new Button.ClickListener() {
            private static final long serialVersionUID = 1L;

            public void buttonClick(Button.ClickEvent event) {
                // close the window by removing it from the parent window
                getApplication().close();
                getParent().removeWindow(getWindow());
            }
        });
        hLayout.addComponent(close);
        closeWindow = new Button("NO", new Button.ClickListener() {
            private static final long serialVersionUID = 1L;

            public void buttonClick(Button.ClickEvent event) {
                // close the window by removing it from the parent window
                getParent().removeWindow(getWindow());
            }
        });
        hLayout.addComponent(closeWindow);
        vLayout.addComponent(hLayout);
        hLayout.setSizeFull();
        vLayout.setComponentAlignment(hLayout, Alignment.MIDDLE_CENTER);
        addComponent(vLayout);
    }
}
При вызове метода getApplication().close(); Vaadin делает redirect по методу setLogoutURL(getURL().getPath() + "j_spring_security_logout"); который дает понят Spring'у что пользователь вышел и спринг "удаляет" все данные связанные с пользователем, в свою очередь он делает redirect в корень / web приложения вот и все:
public class MyVaadinApplication extends Application {
    private Window window;
    public Button button;
    private HeadPanel headPanel;
    @Autowired(required = true)
    private TestBean testBean;

    @Override
    public void init() {
        setTheme("runo");
        window = new Window("Hello Vaadin!");
        setMainWindow(window);
        setLogoutURL(getURL().getPath() + "j_spring_security_logout");
        window.setImmediate(true);

        headPanel = new HeadPanel();
        headPanel.setSizeFull();
        window.addComponent(headPanel);
        Button button = new Button("Click Me");
        button.addListener(new Button.ClickListener() {
            public void buttonClick(ClickEvent event) {
                if (headPanel.hasAnyRole(ROLE_USER)) {
                    Label label = new Label("you are ROLE_USER");
                    window.addComponent(label);
                } else {
                    Label label = new Label("you are not Login");
                    window.addComponent(label);
                }
            }
        });
        window.addComponent(button);
    }
}
Привожу pom.xml c зависимостями
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.vit</groupId>
    <artifactId>VaadinTest11</artifactId>
    <packaging>war</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>Vaadin Web Application</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <vaadin.version>6.6.6</vaadin.version>
        <gwt.version>2.3.0</gwt.version>
        <gwt.plugin.version>2.2.0</gwt.plugin.version>
        <spring.version>3.1.1.RELEASE</spring.version>
        <spring-security.version>3.1.0.RELEASE</spring-security.version>
    </properties>

    <build>
        <finalName>test</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.5</source>
                    <target>1.5</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>

            <!-- A simple Jetty test server at http://localhost:8080/VaadinTest11 can be launched with the Maven goal jetty:run
        and stopped with jetty:stop -->
            <plugin>
                <groupId>org.mortbay.jetty</groupId>
                <artifactId>maven-jetty-plugin</artifactId>
                <version>6.1.24</version>
                <configuration>
                    <stopPort>9966</stopPort>
                    <stopKey>VaadinTest11</stopKey>
                    <!-- Redeploy every x seconds if changes are detected, 0 for no automatic redeployment -->
                    <scanIntervalSeconds>0</scanIntervalSeconds>
                    <!-- make sure Jetty also finds the widgetset -->
                    <webAppConfig>
                        <contextPath>/VaadinTest11</contextPath>
                        <baseResource implementation="org.mortbay.resource.ResourceCollection">
                            <!-- Workaround for Maven/Jetty issue http://jira.codehaus.org/browse/JETTY-680 -->
                            <!-- <resources>src/main/webapp,${project.build.directory}/${project.build.finalName}</resources> -->
                            <resourcesAsCSV>src/main/webapp,${project.build.directory}/${project.build.finalName}
                            </resourcesAsCSV>
                        </baseResource>
                    </webAppConfig>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <repositories>
        <repository>
            <id>vaadin-snapshots</id>
            <url>http://oss.sonatype.org/content/repositories/vaadin-snapshots/</url>
            <releases>
                <enabled>false</enabled>
            </releases>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
        <repository>
            <id>vaadin-addons</id>
            <url>http://maven.vaadin.com/vaadin-addons</url>
        </repository>
    </repositories>

    <dependencies>
        <dependency>
            <groupId>com.vaadin</groupId>
            <artifactId>vaadin</artifactId>
            <version>${vaadin.version}</version>
        </dependency>

        <!-- Spring Dependencies -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
            <exclusions>
                <!-- Exclude Commons Logging in favor of SLF4j -->
                <exclusion>
                    <groupId>commons-logging</groupId>
                    <artifactId>commons-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>${spring.version}</version>
            <type>jar</type>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring.version}</version>
            <type>jar</type>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-support</artifactId>
            <version>2.0.8</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
            <exclusions>
                <exclusion>
                    <artifactId>commons-logging</artifactId>
                    <groupId>commons-logging</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jms</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!-- logging -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.4.2</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-jdk14</artifactId>
            <version>1.4.2</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.16</version>
        </dependency>
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.1.1</version>
        </dependency>

        <dependency>
            <groupId>org.apache.xbean</groupId>
            <artifactId>xbean-spring</artifactId>
            <version>3.6</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.6.6</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.6.6</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjtools</artifactId>
            <version>1.6.6</version>
        </dependency>

        <!-- http://repo1.maven.org/maven -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>jsp-api</artifactId>
            <version>2.1</version>
        </dependency>
        <!--spring-security-->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-web</artifactId>
            <version>${spring-security.version}</version>
            <type>jar</type>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-core</artifactId>
            <version>${spring-security.version}</version>
            <type>jar</type>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-taglibs</artifactId>
            <version>${spring-security.version}</version>
            <type>jar</type>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
            <version>${spring-security.version}</version>
            <type>jar</type>
        </dependency>
    </dependencies>

</project>

Комментариев нет:

Отправить комментарий