乐者为王

Do one thing, and do it well.

使用Interceptor禁止用户访问未授权的图书信息

现在的JBookShelf有两个问题:

  1. 未登录的用户可以访问图书信息;
  2. 登录后的用户可以访问其他用户的图书信息。

第一个问题可以使用Struts 2的Interceptor来解决:

  1. 创建一个实现了Interceptor接口的类;
  2. 在struts.xml配置中定义这个拦截器;
  3. 在struts.xml中定义一个使用了上面拦截器的拦截栈;
  4. 在struts.xml中定义一个全局转向配置。

实现自己的拦截器有点要注意的是,拦截器必须是无状态的,不要使用在API提供的ActionInvocation之外的任何东西。要求拦截器是无状态的原因是Struts 2不能保证为每一个请求或者action创建一个实例,所以如果拦截器带有状态,会引发并发问题。

创建AuthorizationInterceptor类,继承类AbstractInterceptor。为什么继承它呢?而不是直接实现接口Interceptor。这是因为AbstractInterceptor已经实现了Interceptor接口,并且实现了接口中的init和destroy方法。而在这个拦截器中,我们并不需要使用这两个方法。下面上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.codemany.account.interceptor;

import java.util.Collections;
import java.util.Set;

import com.codemany.account.model.User;

import com.opensymphony.xwork2.Action;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
import com.opensymphony.xwork2.util.TextParseUtil;

public class AuthorizationInterceptor extends AbstractInterceptor {
    private static final long serialVersionUID = -5140884040684756043L;

    protected Set<String> skipActions = Collections.emptySet();

    public void setSkipActions(String skipActions) {
        this.skipActions = TextParseUtil.commaDelimitedStringToSet(skipActions);
    }

    @Override
    public String intercept(ActionInvocation invocation) throws Exception {
        User user = (User)invocation.getInvocationContext()
                .getSession().get(User.SESSION_KEY);

        boolean isLogined = user != null;
        String action = invocation.getProxy().getActionName();
        // 如果用户未登录,并且访问的是需要登录权限的Action,就跳转到全局转向配置login上
        if (isLogined || skipActions.contains(action)) {
            return invocation.invoke();
        } else {
            return Action.LOGIN;
        }
    }
}

这里的skipActions目的是为了跳过一些不需要拦截的Action。因为默认情况下,拦截器会拦截Action中的所有的方法。像login,register这类Action是任何用户在任何状态下都可以访问的,所以不需要拦截,这里就可以将这些Action放到skipActions中来跳过拦截。

实现了拦截类后还要在struts.xml进行配置使它起作用,以下是struts.xml的完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
    "http://struts.apache.org/dtds/struts-2.3.dtd">

<struts>
    <constant name="struts.enable.DynamicMethodInvocation" value="true" />
    <constant name="struts.devMode" value="true" />

    <package name="default" extends="struts-default">
        <interceptors>
            <interceptor name="authorization"
                class="com.codemany.account.interceptor.AuthorizationInterceptor" />

            <interceptor-stack name="authorizationStack">
                <interceptor-ref name="authorization">
                    <param name="skipActions">login, logout, register</param>
                </interceptor-ref>
                <interceptor-ref name="defaultStack" />
            </interceptor-stack>
        </interceptors>

        <default-interceptor-ref name="authorizationStack" />

        <!-- 全局转向配置 -->
        <!-- 还记得拦截器里面的return Action.LOGIN这句吧,当程序执行完这一行后,-->
        <!-- 就会到struts.xml文件的global-results中找name为login的全局转向配置。-->
        <global-results>
            <result name="login" type="redirectAction">login!input</result>
        </global-results>
    </package>

    <include file="account.xml" />
    <include file="book.xml" />
</struts>

然后将account.xml和book.xml中package继承的父包改成struts.xml配置中的default包:

1
2
3
<package name="account" extends="default">

<package name="book" extends="default">

第二个问题的解决就是修改BookAction.java的代码,不再从数据库中读取Book数据,而是从当前登录用户的books属性中查找Book信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
package com.codemany.book.action;

import java.util.ArrayList;
import java.util.List;

import com.codemany.account.model.User;
import com.codemany.book.model.Book;
import com.codemany.book.service.BookService;

import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;

public class BookAction extends ActionSupport {
    private static final long serialVersionUID = 2538923417705852774L;

    private Long bookId;
    private Book book;
    private List<Book> bookList;

    private BookService bookService;

    public String list() throws Exception {
        if (bookList == null) {
            bookList = new ArrayList<Book>();
        }
        bookList.addAll(getCurrentUser().getBooks());
        return "list";
    }

    public String show() throws Exception {
        book = getCurrentUser().getBook(bookId);
        return "show";
    }

    public String input() throws Exception {
        if (bookId != null) {
            book = getCurrentUser().getBook(bookId);
        }
        return INPUT;
    }

    public String saveOrUpdate() throws Exception {
        book.setUser(getCurrentUser());
        bookService.saveOrUpdateBook(book);
        return SUCCESS;
    }

    public String delete() throws Exception {
        Book book = getCurrentUser().getBook(bookId);
        if (book != null) {
            bookService.deleteBook(bookId);
            getCurrentUser().getBooks().remove(book);
        }
        return SUCCESS;
    }

    public List<Book> getBookList() {
        return bookList;
    }

    public Book getBook() {
        return book;
    }

    public void setBook(Book book) {
        this.book = book;
    }

    public void setBookId(Long bookId) {
        this.bookId = bookId;
    }

    public void setBookService(BookService bookService) {
        this.bookService = bookService;
    }

    private User getCurrentUser() {
        return (User)ActionContext.getContext()
                .getSession().get(User.SESSION_KEY);
    }
}

上面的代码会产生异常,提示取出的Book数据为空,所以要在User.hbm.xml中set标签后添加属性lazy=“false”,这样Hibernate从数据库中读取User数据时会连带取出对应的Book数据:

1
<set name="books" inverse="true" lazy="false">

下面是BookAction类中用到的User.getBook(bookId)代码:

1
2
3
4
5
6
7
8
9
10
public class User {

    public Book getBook(Long bookId) {
        for (Book book : books) {
            if (bookId != null && bookId.equals(book.getId())) {
                return book;
            }
        }
        return null;
    }

代码下载:https://github.com/dohkoos/JBookShelf

Comments