Spring Security 3.2.x 配置

使用 Spring Security 保护 Web 应用的安全 http://www.ibm.com/developerworks/cn/java/j-lo-springsecurity/
SpringSecurity方法层4种方式使用 http://blog.csdn.net/wyabc1986/article/details/8424688
Spring Security 3.1.4 版本开发解读 http://fantasyeye.iteye.com/blog/1938046
Spring Security 3.1 自定义 authentication provider http://www.xeclipse.com/?p=1359
SSH框架结合Spring Security3新手入门 http://jusesgod.iteye.com/blog/1141408
Spring Security 中的盐值加密 http://nhy520.iteye.com/blog/801488
FilterInvocationSecurityMetadataSource 资源角色授权器需要实现FilterInvocationSecurityMetadataSource接口。请求的资源所需要的角色权限在服务器启动时候就已经确定的,所以在该实现类的构造方法中需要确定每一种资源需要那些角色权限。详细参考:SpringSecurity-----登录用户权限验证demo http://201205164957.iteye.com/blog/1627200和spring security 3 中使用自定义数据库来设置权限 http://blog.csdn.net/remote_roamer/article/details/5713777
Spring Security3 页面 权限标签 http://hehch.iteye.com/blog/1409959
Spring Security默认的403页面 http://www.it161.com/article/javaDetail?articleid=140113232712,两种方法:
1. <access-denied-handler error-page="/403.jsp"/>,这里的error-page好像必须是/符号开头,否则报错的。
2. hanlder方法 http://naozao.com/topic/view/133.html,自定义自己的方法,可以显示更多的信息。
Spring Security 常用的几个自定义filter http://www.quanlei.com/2011/01/2029.html
在spring security获取当前对象 http://zm2011.iteye.com/blog/1319577



SecurityContext 和 Authentication 对象
下面开始讨论几个 Spring Security 里面的核心对象。org.springframework.security.core.context.SecurityContext接口表示的是当前应用的安全上下文。通过此接口可以获取和设置当前的认证对象。org.springframework.security.core.Authentication接口用来表示此认证对象。通过认证对象的方法可以判断当前用户是否已经通过认证,以及获取当前认证用户的相关信息,包括用户名、密码和权限等。要使用此认证对象,首先需要获取到 SecurityContext 对象。通过 org.springframework.security.core.context. SecurityContextHolder 类提供的静态方法 getContext() 就可以获取。再通过 SecurityContext对象的 getAuthentication()就可以得到认证对象。通过认证对象的 getPrincipal() 方法就可以获得当前的认证主体,通常是 UserDetails 接口的实现。联系到上一节介绍的 UserDetailsService,典型的认证过程就是当用户输入了用户名和密码之后,UserDetailsService通过用户名找到对应的 UserDetails 对象,接着比较密码是否匹配。如果不匹配,则返回出错信息;如果匹配的话,说明用户认证成功,就创建一个实现了 Authentication接口的对象,如 org.springframework.security. authentication.UsernamePasswordAuthenticationToken 类的对象。再通过 SecurityContext的 setAuthentication() 方法来设置此认证对象。

5.2.1. SecurityContextHolder, SecurityContext 和 Authentication对象
http://www.fengfly.com/document/springsecurity3/technical-overview.html
最基础的对象就是SecurityContextHolder。 我们把当前应用程序的当前安全环境的细节存储到它里边了, 它也包含了应用当前使用的主体细节。 默认情况下,SecurityContextHolder使用ThreadLocal存储这些信息, 这意味着,安全环境在同一个线程执行的方法一直是有效的, 即使这个安全环境没有作为一个方法参数传递到那些方法里。 这种情况下使用ThreadLocal是非常安全的, 只要记得在处理完当前主体的请求以后,把这个线程清除就行了。 当然,Spring Security自动帮你管理这一切了, 你就不用担心什么了。
有些程序并不适合使用ThreadLocal, 因为它们处理线程的特殊方法。比如,swing客户端也许希望 JVM里的所有线程都使用同一个安全环境。 SecurityContextHolder可以使用一个策略进行配置 在启动时,指定你想让上下文怎样被保存。对于一个单独的应用系统,你可以使用 SecurityContextHolder.MODE_GLOBAL策略。 其他程序可能想让一个线程创建的线程也使用相同的安全主体。 这时可以使用 SecurityContextHolder.MODE_INHERITABLETHREADLOCAL。 想要修改默认的SecurityContextHolder.MODE_THREADLOCAL模式,可以使用两种方法。 第一个是设置系统属性。另一个是调用 SecurityContextHolder的静态方法。大多数程序不需要修改默认值, 但是如果你需要做修改,先看一下 SecurityContextHolder的JavaDoc中的详细信息。
5.2.1.1. 获得当前用户的信息
我们把安全主体和系统交互的信息都保存在 SecurityContextHolder中了。 Spring Security使用一个Authentication对应来表现这些信息。 虽然你通常不需要自己创建一个Authentication对象, 但是常见的情况是,用户查询 Authentication对象。你可以使用下面的代码 - 在你程序中的任何位置 - 来获得已认证用户的名字, 比如:
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();

if (principal instanceof UserDetails) {
  String username = ((UserDetails)principal).getUsername();
} else {
  String username = principal.toString();
}

调用getContext()返回的对象是一个 SecurityContext接口的实例。 这个对象是保存在thread-local中的。如我们下面看到的,大多数Spring Security的验证机制 都返回一个UserDetails的实例 作为主体。
5.2.2. UserDetailsService
从上面的代码片段中还可以看出另一件事,就是你可以从Authentication对象中获得安全主体。 这个安全主体就是一个对象。 大多数情况下,可以强制转换成UserDetails对象。 UserDetails是一个Spring Security的核心接口。 它代表一个主体,是扩展的,而且是为特定程序服务的。 想一下UserDetails章节,在你自己的用户数据库和如何把Spring Security需要的数据放到SecurityContextHolder里。 为了让你自己的用户数据库起作用,我们常常把UserDetails转换成你系统提供的类,这样你就可以直接调用业务相关的方法了(比如getEmail(), getEmployeeNumber()等等)。
现在,你可能想知道,我应该什么时候提供这个UserDetails对象呢? 我怎么做呢? 我想你说这个东西是声明式的,我不需要写任何代码,怎么办? 简单的回答是,这里有一个特殊的接口,叫UserDetailsService。 这个接口里的唯一一个方法,接收String类型的用户名参数,返回UserDetails:
  UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
这是获得从Spring Security中获得用户信息的最常用方法,你会看到它在框架中一直被用到。 当需要获得一个用户的信息的时候。
当成功通过验证时,UserDetails会被用来 建立Authentication对象,保存在SecurityContextHolder里。 (更多的信息可以参考下面的#tech-intro-authentication-mgr)。 好消息是我们提供了好几个UserDetailsService实现,其中一个使用了 内存中的map(InMemoryDaoImpl)另一个而是用了JDBC (JdbcDaoImpl)。 虽然,大多数用户倾向于写自己的,使用这些实现常常放到已有的数据访问对象(DAO)上,表示它们的雇员,客户或其他企业应用中的用户。 记住这个优势,无论你用什么UserDetailsService返回的数据都可以通过SecurityContextHolder获得,就像上面的代码片段讲的一样。
5.2.3. GrantedAuthority
除了主体,另一个Authentication提供的重要方法是getAuthorities()。 这个方法提供了GrantedAuthority对象数组。 毫无疑问,GrantedAuthority是赋予到主体的权限。 这些权限通常使用角色表示,比如ROLE_ADMINISTRATOR 或 ROLE_HR_SUPERVISOR。 这些角色会在后面,对web验证,方法验证和领域对象验证进行配置。 Spring Security的其他部分用来拦截这些权限,期望他们被表现出现。 GrantedAuthority对象通常使用UserDetailsService读取的。
通常情况下,GrantedAuthority对象是应用程序范围下的授权。 它们不会特意分配给一个特定的领域对象。 因此,你不能设置一个GrantedAuthority,让它有权限展示编号54的Employee对象,因为如果有成千上网的这种授权,你会很快用光内存(或者,至少,导致程序花费大量时间去验证一个用户)。 当然,Spring Security被明确设计成处理常见的需求,但是你最好别因为这个目的使用项目领域模型安全功能。
5.2.4. 小结
简单回顾一下,Spring Security主要是由一下几部分组成的:
SecurityContextHolder,提供几种访问SecurityContext的方式。
SecurityContext,保存Authentication信息,和请求对应的安全信息。
HttpSessionContextIntegrationFilter,为了在不同请求使用,把SecurityContext保存到 HttpSession里。
Authentication,展示Spring Security特定的主体。
GrantedAuthority,反应,在应用程序范围你,赋予主体的权限。
UserDetails,通过你的应用DAO,提供必要的信息,构建Authentication对象。
UserDetailsService,创建一个 UserDetails,传递一个 String类型的用户名(或者证书ID或其他)。



文章说明和配置都是参考 Spring Security 3.1.4 版本开发解读

源码:
pom.xml
============================
        <spring.version>4.0.5.RELEASE</spring.version>
        <spring.security.version>3.2.4.RELEASE</spring.security.version>
        ......
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-core</artifactId>
            <version>${spring.security.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
            <version>${spring.security.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-web</artifactId>
            <version>${spring.security.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-crypto</artifactId>
            <version>${spring.security.version}</version>
        </dependency>
        <!--<dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-acl</artifactId>
            <version>${spring.security.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-openid</artifactId>
            <version>${spring.security.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-ldap</artifactId>
            <version>${spring.security.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-taglibs</artifactId>
            <version>${spring.security.version}</version>
        </dependency>-->



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_3_0.xsd"
         version="3.0">

    <display-name>Archetype Created Web Application</display-name>
    <listener>
        <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
    </listener>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath*:applicationContext*.xml</param-value>
    </context-param>


    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>
    <!-- Spring Secutiry 过滤链配置, 此过滤器必须配置,不然无法使用 Spring Security 框架对访问权限的控制。 -->
    <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>
    <filter>
        <filter-name>openSessionInViewFilter</filter-name>
        <filter-class>org.springframework.orm.hibernate4.support.OpenSessionInViewFilter</filter-class>
    </filter>
    <welcome-file-list>
        <welcome-file>login.jsp</welcome-file>
    </welcome-file-list>
</web-app>




数据库
===============================
drop table if exists sys_role_authoritie_mapping;

drop table if exists sys_user_role_mapping;

drop table if exists sys_authoritie;

drop index Index_1 on sys_role;

drop table if exists sys_role;

drop index Index_1 on sys_user;

drop table if exists sys_user;

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for sys_authoritie
-- ----------------------------
CREATE TABLE `sys_authoritie` (
  `auth_id` int(11) NOT NULL AUTO_INCREMENT,
  `description` varchar(100) DEFAULT NULL,
  `url` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`auth_id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of sys_authoritie
-- ----------------------------
INSERT INTO `sys_authoritie` VALUES ('1', 'add', '/stc/add.do');
INSERT INTO `sys_authoritie` VALUES ('2', 'save', '/stc/save.do');
INSERT INTO `sys_authoritie` VALUES ('3', 'update', '/stc/update.do');
INSERT INTO `sys_authoritie` VALUES ('4', 'delete', '/stc/delete.do');

-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
CREATE TABLE `sys_role` (
  `role_id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`role_id`),
  UNIQUE KEY `Index_1` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES ('1', 'admin');
INSERT INTO `sys_role` VALUES ('3', 'custom');
INSERT INTO `sys_role` VALUES ('2', 'user');

-- ----------------------------
-- Table structure for sys_role_authoritie_mapping
-- ----------------------------
CREATE TABLE `sys_role_authoritie_mapping` (
  `ra_mapping_id` int(11) NOT NULL AUTO_INCREMENT,
  `role_id` int(11) DEFAULT NULL,
  `auth_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`ra_mapping_id`),
  KEY `FK_Reference_3` (`role_id`),
  KEY `FK_Reference_4` (`auth_id`),
  CONSTRAINT `FK_Reference_3` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`role_id`),
  CONSTRAINT `FK_Reference_4` FOREIGN KEY (`auth_id`) REFERENCES `sys_authoritie` (`auth_id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of sys_role_authoritie_mapping
-- ----------------------------
INSERT INTO `sys_role_authoritie_mapping` VALUES ('1', '1', '1');
INSERT INTO `sys_role_authoritie_mapping` VALUES ('2', '2', '2');
INSERT INTO `sys_role_authoritie_mapping` VALUES ('3', '2', '3');
INSERT INTO `sys_role_authoritie_mapping` VALUES ('5', '2', '4');

-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
CREATE TABLE `sys_user` (
  `user_id` int(11) NOT NULL AUTO_INCREMENT,
  `user_name` varchar(100) DEFAULT NULL,
  `password` varchar(100) DEFAULT NULL,
  `email` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`user_id`),
  UNIQUE KEY `Index_1` (`user_name`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES ('1', 'pandy', '31a6a915d2ce9ac21305a06872582cca', 'pandy');
INSERT INTO `sys_user` VALUES ('2', 'pyzheng', '52da327171aaa6813acc027fc242513d', 'pyzheng');
INSERT INTO `sys_user` VALUES ('3', 'test', '889255f1c9c8f12a353be255f78a848b', 'rest');

-- ----------------------------
-- Table structure for sys_user_role_mapping
-- ----------------------------
CREATE TABLE `sys_user_role_mapping` (
  `ur_mapping_id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) DEFAULT NULL,
  `role_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`ur_mapping_id`),
  KEY `FK_Reference_1` (`user_id`),
  KEY `FK_Reference_2` (`role_id`),
  CONSTRAINT `FK_Reference_1` FOREIGN KEY (`user_id`) REFERENCES `sys_user` (`user_id`),
  CONSTRAINT `FK_Reference_2` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`role_id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of sys_user_role_mapping
-- ----------------------------
INSERT INTO `sys_user_role_mapping` VALUES ('1', '1', '1');
INSERT INTO `sys_user_role_mapping` VALUES ('2', '2', '2');
INSERT INTO `sys_user_role_mapping` VALUES ('3', '3', '3');





省略掉了其他applicationContext的xml配置文件,只给security的配置
applicationContext-security.xml
==================================================
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:security="http://www.springframework.org/schema/security"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       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.2.xsd">

    <!-- 登录页面不过滤 , 如果你不需要对某些访问路径,进行权限控制-->
    <security:http pattern="/login" security="none"/>
    <security:http auto-config="true">
        <!-- 登录配置 -->
        <security:form-login login-page="/login.jsp"
                             authentication-failure-url="/login.jsp?err=true"
                             default-target-url="/index.jsp"
                             username-parameter="j_username"
                             password-parameter="j_password"
                             login-processing-url="/j_spring_security_check"/>
        <!-- 退出配置 -->
        <!--
            logout-url:退出请求地址。系统默认:j_spring_security_logout
            logout-success-url:退出成功,跳转地址连接。
            delete-cookies:删除 cookies 内容。
            success-handler-ref:退出回调接口。类需实现接口: LogoutSuccessHandler
            invalidate-session:如果设置为 true,用户的 Session 将会在退出时被失效。
         -->
        <security:logout logout-success-url="/logout.jsp" invalidate-session="true"/>
        <!-- 自定义没有权限的页面,Spring Security默认的403页面 -->
        <!--<security:access-denied-handler error-page = "/403.jsp"/>-->
        <security:access-denied-handler ref="accessDeniedHandler"/>
        <!--<security:remember-me/>-->
        <!-- 只能登陆一次 -->
        <security:session-management session-authentication-error-url="/402.jsp" invalid-session-url="/sessionTimeout.jsp">
            <security:concurrency-control max-sessions="1" error-if-maximum-exceeded="true"/>
        </security:session-management>

        <!--<remember-me data-source-ref="dataSource" />-->
        <!-- 自定义过滤器, 实现用户、角色、权限、资源的数据库管理 -->
        <security:custom-filter ref="mySecurityFilter" before="FILTER_SECURITY_INTERCEPTOR"/>
    </security:http>

    <bean id="accessDeniedHandler" class="com.security.LaihecaiAccessDeniedHandler">
        <property name="accessDeniedUrl" value="/SpringSecurity/403.jsp" />
    </bean>

    <!-- 自定义过滤器 -->
    <bean id="mySecurityFilter" class="com.security.MySecurityInterceptor">
        <!-- FilterInvocationSecurityMetadataSource 接口实现类, 资源源数据定义 -->
        <property name="securityMetadataSource" ref="mySecurityMetadataSource"/>
        <!-- 鉴定管理类,跟用户信息信息有关 -->
        <property name="authenticationManager" ref="myAuthenticationManager"/>
        <!-- AccessDecisionManager 接口实现类, 角色跟资源的关系 -->
        <!-- 访问决策器,决定某个用户具有的角色,是否有足够的权限去访问某个资源     -->
        <property name="accessDecisionManager" ref="myAccessDecisionManager"/>
    </bean>

    <!-- 鉴定管理类配置信息 -->
    <security:authentication-manager alias="myAuthenticationManager">
        <!-- 鉴定管理类 -->
        <security:authentication-provider user-service-ref="myUserDetailsService">
            <!-- 用户详情管理类 [UserDetailsService 接口 实现类] -->
            <security:password-encoder ref="passwdEcoder">
                <!-- 用户加密解密类, 将每个用户的username作为盐值 -->
                <security:salt-source user-property="username"/>
            </security:password-encoder>
        </security:authentication-provider>
    </security:authentication-manager>
    <!-- 用户详细信息获取接口 -->
    <bean id="myUserDetailsService" class="com.security.MyUserDetailsService"/>
    <!-- 访问决策器, 决定某个用户具体的角色,是否有足够的权限访问某个资源 -->
    <bean id="myAccessDecisionManager" class="com.security.MyAccessDecisionManager"/>
    <!-- 资源源数据定义, 将所有的资源和权限的对应关系建立起来,即定义某一资源可以被哪些角色去访问。-->
    <bean id="mySecurityMetadataSource" class="com.security.MySecurityMetadataSource"/>
    <!-- 用户详情管理类使用的加密方式 -->
    <bean id="passwdEcoder" class="org.springframework.security.authentication.encoding.Md5PasswordEncoder"/>
    <!-- PasswordEncoder 密码接口 -->
    <bean id="PasswordUtil" class="com.utils.PasswordUtil" lazy-init="false"/>
    <bean id="SpringSecurityUtil" class="com.utils.SpringSecurityUtil" lazy-init="false"/>
</beans>

<custom-filterref="myFilter"before="FILTER_SECURITY_INTERCEPTOR"/>这里的FILTER_SECURITY_INTERCEPTOR是SpringSecurity默认的Filter,
我们自定义的Filter必须在它之前,过滤客服请求。用一个继承AbstractSecurityInterceptor实现类来完成这个工作。


一个错误转向过滤器和四个security的实现类
============================================================
package com.security;

import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 配置没有权限访问页面的跳转信息
 */
public class LaihecaiAccessDeniedHandler implements AccessDeniedHandler {
    private static String ACCESS_DENIED_MSG = "message";
    private String accessDeniedUrl;

    public LaihecaiAccessDeniedHandler() {

    }
    public LaihecaiAccessDeniedHandler(String accessDeniedUrl) {

        this.accessDeniedUrl = accessDeniedUrl;

    }

    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        response.sendRedirect(accessDeniedUrl);
        String deniedMessage = accessDeniedException.getMessage();
        String rp = request.getRequestURI();
        request.getSession().setAttribute(ACCESS_DENIED_MSG, deniedMessage);
    }


    public String getAccessDeniedUrl() {

        return accessDeniedUrl;

    }


    public void setAccessDeniedUrl(String accessDeniedUrl) {

        this.accessDeniedUrl = accessDeniedUrl;

    }

}








编写自定义过滤器,必须继承 org.springframework.security.access.intercept.AbstractSecurityInterceptor 和 实现 javax.servlet.Filter 接口。重写 doFilter 方法并新增属性 FilterInvocationSecurityMetadataSource 对象的 getter 和 setter 方法,用于 Spring 注入。
package com.security;

import org.springframework.security.access.SecurityMetadataSource;
import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
import org.springframework.security.access.intercept.InterceptorStatusToken;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;

import javax.servlet.*;
import java.io.IOException;

/**
 * @author
 * @version 1.0
 * @ClassName MySecurityInterceptor
 * @Description TODO
 * @date 2013-9-5 上午10:20:11
 */
public class MySecurityInterceptor extends AbstractSecurityInterceptor
        implements Filter {

    private FilterInvocationSecurityMetadataSource securityMetadataSource;

    /**
     * @param filterConfig
     * @throws ServletException
     */
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

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

        FilterInvocation fi = new FilterInvocation(request, response, chain);
        invoke(fi);
    }


    public void invoke(FilterInvocation fi) {
        InterceptorStatusToken token = super.beforeInvocation(fi);

        try {
            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
        } catch (Exception e) {
            super.afterInvocation(token, null);
        }

    }


    /**
     *
     */
    @Override
    public void destroy() {
    }

    /**
     * @return
     */
    @Override
    public Class<? extends Object> getSecureObjectClass() {
        return FilterInvocation.class;
    }

    /**
     * @return
     */
    @Override
    public SecurityMetadataSource obtainSecurityMetadataSource() {
        return this.securityMetadataSource;
    }

    public void setSecurityMetadataSource(FilterInvocationSecurityMetadataSource securityMetadataSource) {
        this.securityMetadataSource = securityMetadataSource;
    }

}








如之前的配置,我们需要 Spring 帮我们注入 securityMetadataSource、authenticationManager、accessDecisionManager 三个类对象。那他们分别是做什么的呢?现在听我慢慢道来。
securityMetadataSource 这个类型的接口,提供了根据访问资源获取角色集合的接口,也就是说此类维护着,资源和角色的关系并提供外界使用。 securityMetadataSource 必须是 FilterInvocationSecurityMetadataSource 接口实现类。看看我写的自定义实现类:
在 loadResourceDefine 方法中,初始化了资源。将资源和权限以 Map 的形式做了映射。一个地址会对应一组权限。然后实现了接口方法 public Collection<ConfigAttribute> getAttributes(Object object) ,可以通过此方法获取权限集合。这个接口的调用在 AbstractSecurityInterceptor 的 beforeInvocation 方法中。

package com.security;

import com.service.SysAuthoritieService;
import org.apache.log4j.Logger;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.util.AntPathRequestMatcher;
import org.springframework.security.web.util.RequestMatcher;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;

/**
 * 资源源数据定义, 将所有的资源和权限的对应关系建立起来,即定义某一资源可以被哪些角色去访问。
 * securityMetadataSource 这个类型的接口,
 * 提供了根据访问资源获取角色集合的接口,
 * 也就是说此类维护着,
 * 资源和角色的关系并提供外界使用。
 * securityMetadataSource 必须是 FilterInvocationSecurityMetadataSource 接口实现类。
 * <p/>
 * 我在 loadResourceDefine 方法中,初始化了资源。将资源和权限以 Map 的形式做了映射。
 * 一个地址会对应一组权限。然后实现了接口方法 public Collection<ConfigAttribute> getAttributes(Object object) ,
 * 可以通过此方法获取权限集合。
 * 这个接口的调用在 AbstractSecurityInterceptor 的 beforeInvocation 方法中。
 *
 * @version 1.0
 * @ClassName MySecurityMetadataSource
 * @Description TODO
 * @date 2013-9-5 上午10:50:30
 */
public class MySecurityMetadataSource implements
        FilterInvocationSecurityMetadataSource {

    //这里被延时初始化
    @Resource(name = "sysAuthoritieService")
    private SysAuthoritieService sysAuthoritieService;

    /**
     * LOGGER 日志对象
     */
    private final static Logger LOGGER = Logger.getLogger(MySecurityMetadataSource.class);

    //资源和权限以 Map 的形式做了映射
    private HashMap<String, Collection<ConfigAttribute>> map = null;

    /**
     * 加载资源,初始化资源变量
     */
    private void loadResourceDefine() {
        if (sysAuthoritieService != null) {
            //TODO: 应该使用有事务的service去读取资源和角色的映射
            map = sysAuthoritieService.loadResourceDefine();

            /*Collection<ConfigAttribute> array = new ArrayList<ConfigAttribute>(4);

            ConfigAttribute cfg = new SecurityConfig("a1");
            array.add(cfg);

            cfg = new SecurityConfig("a2");
            array.add(cfg);

            cfg = new SecurityConfig("a3");
            array.add(cfg);

            cfg = new SecurityConfig("a4");
            array.add(cfg);
            map.put("/demo/list", array);

            array = new ArrayList<ConfigAttribute>(4);

            cfg = new SecurityConfig("n1");
            array.add(cfg);

            cfg = new SecurityConfig("n2");
            array.add(cfg);
            map.put("/demo/news", array);*/
        }
    }


    public MySecurityMetadataSource() {
        loadResourceDefine();
    }

    /**
     * 根据路径获取访问权限的集合接口
     * @param object
     * @return
     * @throws IllegalArgumentException
     */
    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {

        if (map == null) {
            loadResourceDefine();
        }
        LOGGER.info(object);

        HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
        RequestMatcher matcher = null;
        String resUrl = null;
        for (Iterator<String> iter = map.keySet().iterator(); iter.hasNext(); ) {
            resUrl = iter.next();
            matcher = new AntPathRequestMatcher(resUrl);
            if (null != resUrl && matcher.matches(request)) {
                return map.get(resUrl);
            }
        }
        return null;
    }

    /**
     * @return
     */
    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }

    /**
     * @param clazz
     * @return
     */
    @Override
    public boolean supports(Class<?> clazz) {
        return true;
    }

}







读取资源信息的service,
实现类中,我们可以通过 loadUserByUsername 方法,根据用户名找到该用户的基本信息和角色信息。并创建 UserDetails 实现类对象返回。我们在这里设置了角色集合对象 array 并将其赋值给了User 对象。
==================================================
package com.security;

import com.dao.SysUserDao;
import com.google.common.collect.Maps;
import com.model.SysUser;
import org.springframework.dao.DataAccessException;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.Map;

/**
 * 用户详细信息获取接口
 * @author 实现类中,我们可以通过 loadUserByUsername 方法,
 *         根据用户名找到该用户的基本信息和角色信息。
 *         并创建 UserDetails 实现类对象返回。
 *         我们在这里设置了角色集合对象 array 并将其赋值给了User 对象。
 * @version 1.0
 * @ClassName MyUserDetailsService
 * @Description TODO
 * @date 2013-9-5 下午1:19:33
 */
@Transactional
public class MyUserDetailsService implements UserDetailsService {

    @Resource(name = "sysUserDao")
    private SysUserDao sysUserDao;

    /**
     * @param username
     * @return
     * @throws UsernameNotFoundException
     * @throws DataAccessException
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = RuntimeException.class)
    public UserDetails loadUserByUsername(String username)
            throws UsernameNotFoundException, DataAccessException {
        System.out.println("后台信息:    用户[" + username + "]登录请求");
        /*ArrayList<GrantedAuthority> array = new ArrayList<GrantedAuthority>();
        GrantedAuthority ga = new SimpleGrantedAuthority("a1");
        array.add(ga);
        return new User("demo", "8ae29a58361c8b3ec237ae8419df7e58", true, true, true, true, array);*/

        Map<String, Object> params = Maps.newHashMap();
        params.put("userName", username);
        SysUser sysUser = sysUserDao.searchFirstByFields(params);
        return new User(sysUser.getUsername(), sysUser.getPassword(), true, true, true, true, sysUser.getAuthorities());

    }

}






当用户需要访问某个资源是,我们就可以通过这两个对象在 accessDecisionManager 引用的 AccessDecisionManager 接口实现类中,进行比较了。
package com.security;

import org.apache.log4j.Logger;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;

import java.util.Collection;
import java.util.Iterator;

/**
 * 访问决策器, 决定某个用户具体的角色,是否有足够的权限访问某个资源
 * @author decide 方法接受三个参数,
 *         第一个使用户拥有的角色,
 *         第二个参数就是在 过滤器中新建的 FilterInvocation 对象,
 *         第三个参数就是该访问路径对应的角色集合。
 *         然后可以根据 用户角色和菜单角色对比,判断该用户是否具有该路径的访问资格。如果没有,抛出异常。
 * @version 1.0
 * @ClassName MyAccessDecisionManager
 * @Description TODO
 * @date 2013-9-5 上午11:46:35
 */
public class MyAccessDecisionManager implements AccessDecisionManager {

    /**
     * LOGGER 日志对象
     */
    private final static Logger LOGGER = Logger.getLogger(MyAccessDecisionManager.class);

    /**
     * decide 方法接受三个参数,
     * 第一个使用户拥有的角色,
     * 第二个参数就是在 过滤器中新建的 FilterInvocation 对象,
     * 第三个参数就是该访问路径对应的角色集合。
     * 然后可以根据 用户角色和菜单角色对比,判断该用户是否具有该路径的访问资格。
     * 如果没有,抛出异常。
     * @param authentication  //用户的角色信息
     * @param object  //当前被访问的菜单
     * @param configAttributes  //菜单对应的角色
     * @throws AccessDeniedException
     * @throws InsufficientAuthenticationException
     */
    @Override
    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
        LOGGER.info("MyAccessDecisionManager.decide");
        if (null == configAttributes || configAttributes.size() <= 0) {
            return;
        }

        for (Iterator<ConfigAttribute> iter = configAttributes.iterator(); iter.hasNext(); ) {
            ConfigAttribute c = iter.next();
            String needRole = c.getAttribute();

            LOGGER.info("菜单["+object.toString()+"]访问权限:" + needRole);
            for (GrantedAuthority ga : authentication.getAuthorities()) {
                if (needRole.trim().equals(ga.getAuthority())) {
                    return;
                }
            }
        }

        throw new AccessDeniedException("没有权限访问这个菜单, 结束。");
    }

    /**
     * @param attribute
     * @return
     */
    @Override
    public boolean supports(ConfigAttribute attribute) {
        return true;
    }

    /**
     * @param clazz
     * @return
     */
    @Override
    public boolean supports(Class<?> clazz) {
        return true;
    }

}



两个简单的工具类:PasswordUtil,SpringSecurityUtil
package com.utils;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.security.authentication.encoding.Md5PasswordEncoder;
import org.springframework.web.context.ServletContextAware;

import javax.servlet.ServletContext;

/**
 * Created by Administrator on 14-6-16.
 */
public class PasswordUtil implements ApplicationContextAware, ServletContextAware {
    private static ServletContext servletContext;
    private static ApplicationContext applicationContext;

    @Override
    public void setServletContext(ServletContext servletContext) {
        PasswordUtil.servletContext = servletContext;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        PasswordUtil.applicationContext = applicationContext;
    }

    public static String encodePassword(String password){
        return encodePassword(password,null);
    }
    public static String encodePassword(String password, String salt){
        Md5PasswordEncoder md5PasswordEncoder = (Md5PasswordEncoder)applicationContext.getBean("passwdEcoder");
        return md5PasswordEncoder.encodePassword(password,salt);
    }
}


package com.utils;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetails;
import org.springframework.web.context.ServletContextAware;

import javax.servlet.ServletContext;
import java.util.List;

/**
 * Created by Administrator on 14-6-17.
 */
public class SpringSecurityUtil implements ApplicationContextAware, ServletContextAware {
    private static ServletContext servletContext;
    private static ApplicationContext applicationContext;


    @Override
    public void setServletContext(ServletContext servletContext) {
        SpringSecurityUtil.servletContext = servletContext;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringSecurityUtil.applicationContext = applicationContext;
    }

    public static UserDetails getLoginUser() {
        UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext()
                .getAuthentication()
                .getPrincipal();
        return userDetails;
    }

    /**
     * 读取当前用户的信息
     */
    public static String getUserName() {
        return SecurityContextHolder.getContext().getAuthentication().getName();
    }

    /**
     * 答应security的一些信息
     */
    public static void printlSecurityInfo() {
        SecurityContext context = SecurityContextHolder.getContext();
        //登录名
        System.out.println("Username=" + context.getAuthentication().getName());
        //登录密码,未加密的
        System.out.println("Credentials=" + context.getAuthentication().getCredentials());
        WebAuthenticationDetails details = (WebAuthenticationDetails) context.getAuthentication().getDetails();
        //获得访问地址
        System.out.println("RemoteAddress=" + details.getRemoteAddress());
        //获得sessionid
        System.out.println("SessionId=" + details.getSessionId());
        //获得当前用户所拥有的权限
        List<GrantedAuthority> authorities = (List<GrantedAuthority>) context.getAuthentication().getAuthorities();
        for (GrantedAuthority grantedAuthority : authorities) {
            System.out.println("Authority=" + grantedAuthority.getAuthority());
        }
    }


}


一个查询资源的service, 资源源数据定义, 将所有的资源和权限的对应关系建立起来
==================================
package com.service.impl;

import com.dao.SysAuthoritieDao;
import com.google.common.collect.Maps;
import com.model.SysAuthoritie;
import com.model.SysRoleAuthoritieMapping;
import com.service.SysAuthoritieService;
import com.utils.PasswordUtil;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.*;

/**
 * Created by Administrator on 14-6-16.
 */
@Service("sysAuthoritieService")
@Transactional
public class SysAuthoritieServiceImpl implements SysAuthoritieService {
    //这里被延时初始化
    @Resource(name = "sysAuthoritieDao")
    private SysAuthoritieDao sysAuthoritieDao;

    @Override
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = RuntimeException.class)
    public List<SysAuthoritie> getAll() {
        return sysAuthoritieDao.getAll();
    }

    /**
     * 将资源和权限以 Map 的形式做了映射。一个地址会对应一组权限。
     * @return
     */
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = RuntimeException.class)
    public HashMap<String, Collection<ConfigAttribute>> loadResourceDefine() {
        HashMap<String, Collection<ConfigAttribute>> map=null;
            map = Maps.newHashMap();
            System.out.println("password=" + PasswordUtil.encodePassword("pandy", "pandy"));
            System.out.println("pyzheng=" + PasswordUtil.encodePassword("pyzheng", "pyzheng"));
            System.out.println("test=" + PasswordUtil.encodePassword("test", "test"));
            List<SysAuthoritie> mappingDaoList = this.getAll();
            System.out.println(mappingDaoList.size());

            if(mappingDaoList!=null){
                for(SysAuthoritie authoritie :mappingDaoList){
                    Collection<ConfigAttribute> array = new ArrayList<ConfigAttribute>(mappingDaoList.size());
                    Iterator<SysRoleAuthoritieMapping> iterator=authoritie.getSysRoleAuthoritieMappings().iterator();
                    while(iterator.hasNext()){
                        SysRoleAuthoritieMapping mapping=iterator.next();
                        ConfigAttribute cfg = new SecurityConfig(mapping.getSysRole().getName());
                        array.add(cfg);
                    }
                    map.put(authoritie.getUrl(), array);
                }
            }
        return map;
    }
}




controller
=============================
package com.controller;

import com.utils.PasswordUtil;
import com.utils.SpringSecurityUtil;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * Created by Administrator on 14-6-16.
 */
@Controller
@RequestMapping("/stc")
public class SecurityTestController {

    private static String getCurrentTime(){
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd mm:ss");
        Date date = new Date();
        return format.format(date);
    }

    @RequestMapping(value = "/add")
    public ModelAndView add() {
        //工具类的测试
        System.out.println(PasswordUtil.encodePassword("pandy","pandy"));
        UserDetails userDetails = SpringSecurityUtil.getLoginUser();
        SpringSecurityUtil.printlSecurityInfo();
        ModelAndView view = new ModelAndView("index");
        view.addObject("message", getCurrentTime() + " 已经进入[add]页面 user="+userDetails.getUsername());
        return view;
    }

    @RequestMapping(value = "/save")
    public ModelAndView save(){
        ModelAndView view = new ModelAndView("index");
        view.addObject("message",getCurrentTime()+" 已经进入[save]页面");
        return view;
    }

    @RequestMapping(value = "/update")
    public ModelAndView update(){
        ModelAndView view = new ModelAndView("index");
        view.addObject("message",getCurrentTime()+" 已经进入[update]页面");
        return view;
    }

    @RequestMapping(value = "/delete")
    public ModelAndView delete(){
        ModelAndView view = new ModelAndView("index");
        view.addObject("message",getCurrentTime()+" 已经进入[delete]页面");
        return view;
    }

}





login.jsp
============================
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title></title>
  </head>
  <body>
    <form method="post" action="/SpringSecurity/j_spring_security_check">
        name:<input type="text" name="j_username"><br>
        pwd:<input type="password" name="j_password"><br>
        <button type="submit">Login</button><br>
    </form>
  </body>
</html>




测试页面:index.jsp
======================================
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title></title>
</head>
<body>
<a href="/SpringSecurity/stc/add.do">add</a>
<a href="/SpringSecurity/stc/save.do">save</a>
<a href="/SpringSecurity/stc/update.do">update</a>
<a href="/SpringSecurity/stc/delete.do">delete</a>

<div>${message}</div>
</body>
</html>

猜你喜欢

转载自panyongzheng.iteye.com/blog/2080390