Spring MVC 项目搭建 -6- spring security使用自定义Filter实现验证扩展url验证,使用数据库进行配置

实现的主要流程

1.创建一个Filter 继承 AbstractSecurityInterceptor

  • FilterSecurityInterceptor extends AbstractSecurityInterceptor

    • 是过滤链中最后一个Filter
    • 使用SecurityContext 和 Authentication 对象,来进行辨别用户的 GrantedAuthority。
  • 为了实现自定义过滤,需要Authentication和SecurityContext,所以继承AbstractSecurityInterceptor,并且放在FilterSecurityInterceptor之前

2.该过滤器需要注入FilterInvocationSecurityMetadataSource,AccessDecisionManager 和 UserDetailsService

  • FilterInvocationSecurityMetadataSource 在spring初始化时获取用户权限资源
  • AccessDecisionManager 用于判断当前用户是否有权限
  • UserDetailsService 用于根据当前用户获取其权限

3.扩展 FilterInvocationSecurityMetadataSource,AccessDecisionManager 和 UserDetailsService,使其使用数据库的数据

自定义Filter-AppFilterSecurityInterceptor

AppFilterSecurityInterceptor

/**
 * 该拦截器用以添加资源拦截
 *
 * 添加一个拦截器,配置在 FILTER_SECURITY_INTERCEPTOR之前
 * 继承本来最后的 AbstractSecurityInterceptor以实现 登录过程
 *
 *
 * 过滤器依赖于 SecurityContext 和 Authentication 对象,来进行辨别用户的 GrantedAuthority。
 * 所以,我们要将这个过滤器的位置放在 FilterSecurityInterceptor 之前
 *
 */
public class AppFilterSecurityInterceptor extends AbstractSecurityInterceptor
        implements Filter {

    private FilterInvocationSecurityMetadataSource securityMetadataSource;
    //拦截器入口
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        FilterInvocation fi = new FilterInvocation(request, response, chain);
        invoke(fi);
    }
    /**
     * fi里面有一个被拦截的url
     * 调用MyInvocationSecurityMetadataSource的getAttributes(Object object)
     * 这个方法获取fi对应的所有权限
     * 再调用MyAccessDecisionManager的decide方法来校验用户的权限是否足够
     */
    public void invoke(FilterInvocation fi) throws IOException,
            ServletException {
        InterceptorStatusToken token = super.beforeInvocation(fi);
        try {
            // 执行下一个拦截器
            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
        } finally {
            super.afterInvocation(token, null);
        }
    }

    //实现接口的方法

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

    public SecurityMetadataSource obtainSecurityMetadataSource() {
        return this.securityMetadataSource;
    }

    public void destroy() {

    }

    public void init(FilterConfig arg0) throws ServletException {

    }

    //用以注入securityMetadataSource
    public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {
        return this.securityMetadataSource;
    }

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

配置Filter - spring-sample-security.xml

<!--忽略http的其他配置,此处只用于说明如何配置Filter->
<http auto-config="true" use-expressions="true" >
    <custom-filter ref="appFilter" before="FILTER_SECURITY_INTERCEPTOR" />
</http>  

<!--自定义一个拦截器  -->
<beans:bean id="appFilter"
    class="security.filter.AppFilterSecurityInterceptor">
    <beans:property name="authenticationManager" ref="appAuthenticationManager" />
    <beans:property name="accessDecisionManager" ref="appAccessDecisionManagerBean" />
    <beans:property name="securityMetadataSource" ref="appSecurityMetadataSource" />
</beans:bean>

扩展FilterInvocationSecurityMetadataSource

AppInvocationSecurityMetadataSource

/**
 * 配置用户角色
 */
public class AppInvocationSecurityMetadataSource implements
        FilterInvocationSecurityMetadataSource {
    private ISecurityResourcesDao resourcesDao;
    // resourceMap 为 资源权限的集合 key-url,value-Collection<ConfigAttribute>
    private static Map<String, Collection<ConfigAttribute>> resourceMap = null;

    public AppInvocationSecurityMetadataSource(ISecurityResourcesDao resourcesDao) {
        this.resourcesDao = resourcesDao;
        loadResourceDefine();
    }
    // 初始化 所有资源与权限的关系
    private void loadResourceDefine() {
        if (resourceMap == null) {
            resourceMap = new HashMap<String, Collection<ConfigAttribute>>();
            //1.查找所有资源
            List<SecurityResource> resources = resourcesDao.findAll();
            //2.查找资源需要角色
            for (SecurityResource resource : resources) {
                Collection<ConfigAttribute> roles = resourcesDao.loadRoleByResource(resource.getResource());
                System.out.println("权限=" + roles+" 资源:"+resource.getResource());
                //缓存数据
                resourceMap.put(resource.getResource(), roles);
            }
        }
    }

    // 核心方法,获取url 所需要的权限(角色)
    public Collection<ConfigAttribute> getAttributes(Object object)
            throws IllegalArgumentException {
        FilterInvocation filterInvocation =  ((FilterInvocation) object);
        // 将参数转为url
        String url = ((FilterInvocation) object).getRequestUrl();
        System.out.println(url);
        //循环已有的角色配置对象 进行url匹配
        Iterator<String> ite = resourceMap.keySet().iterator();
        while (ite.hasNext()) {
            String resURL = ite.next();
            RequestMatcher urlMatcher = new AntPathRequestMatcher(resURL);
            if (urlMatcher.matches(filterInvocation.getHttpRequest())) {
                System.out.println("map:"+resURL + "need:" +resourceMap.get(resURL));
                return resourceMap.get(resURL);
            }
        }
        return null;
    }

    //接口必须实现的方法
    public boolean supports(Class<?> clazz) {
        return true;
    }

    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }
}

配置appSecurityMetadataSource - spring-sample-security.xml

<!--资源源数据定义,将所有的资源和权限对应关系建立起来,即定义某一资源可以被哪些角色访问-->
<beans:bean id="appSecurityMetadataSource"
    class="security.filter.properties.AppInvocationSecurityMetadataSource" >
<beans:constructor-arg name="resourcesDao" ref="securityResourcesDao"/>
</beans:bean>  

扩展AccessDecisionManager

AppAccessDecisionManager

/**
 * Filter 的资源决策管理器
 *
 * 用以决定是否有权限访问该请求
 */
public class AppAccessDecisionManager implements AccessDecisionManager {

    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
            throws AccessDeniedException, InsufficientAuthenticationException {
        if (configAttributes == null) {
            return;
        }

        //所请求的资源拥有的权限(一个资源对多个权限)
        Iterator<ConfigAttribute> ite = configAttributes.iterator();

        while (ite.hasNext()) {

            ConfigAttribute ca = ite.next();
            //访问所请求资源所需要的权限
            String needRole = ((SecurityConfig) ca).getAttribute();
            System.out.println("needRole is " + needRole);
            // ga 为用户所被赋予的权限。 needRole 为访问相应的资源应该具有的权限。
            for (GrantedAuthority ga : authentication.getAuthorities()) {
                if (needRole.trim().equals(ga.getAuthority().trim())) {
                    return;
                }
            }
        }
        //没有权限
        throw new AccessDeniedException("没有权限访问!");

    }

    public boolean supports(ConfigAttribute attribute) {
        return true;
    }

    public boolean supports(Class<?> clazz) {
        return true;
    }
}

配置appAccessDecisionManager -spring-sample-security.xml

<!--访问决策器,决定某个用户具有的角色,是否有足够的权限去访问某个资源 -->
<beans:bean id="appAccessDecisionManagerBean"
    class="security.filter.properties.AppAccessDecisionManager">
</beans:bean> 

扩展UserDetailsService

AppUserDetailService(在项目搭建5已经修改)

配置(在项目搭建5已经修改)

添加数据库的表格(其他表 在项目搭建5已经添加)

--添加资源列表SQL
CREATE TABLE security_resource (
  id int NOT NULL auto_increment,
  name varchar(50) DEFAULT NULL,
  type varchar(50) DEFAULT NULL,
  resource varchar(200) DEFAULT NULL,
  description varchar(200) DEFAULT NULL,
  PRIMARY KEY (id)
) ;

CREATE TABLE security_resource_role (
  resource_id int not NULL,
  role_id int not NULL,
  PRIMARY KEY (resource_id,role_id),
  CONSTRAINT security_resource_role_ibfk_1 FOREIGN KEY (resource_id) REFERENCES security_resource (id) ON DELETE CASCADE ON UPDATE CASCADE,
  CONSTRAINT security_resource_role_ibfk_2 FOREIGN KEY (role_id) REFERENCES security_role (id) ON DELETE CASCADE ON UPDATE CASCADE
) ;

INSERT INTO security_resource (name,type,resource,description) VALUES ('', 'URL', '/app/*', 'app controller');
INSERT INTO security_resource (name,type,resource,description) VALUES ('', 'URL', '/test/*', 'test controller');
INSERT INTO security_resource (name,type,resource,description) VALUES ('', 'URL', '/**', 'all resources');

INSERT INTO security_resource_role (resource_id,role_id) VALUES ('1', '1');
INSERT INTO security_resource_role (resource_id,role_id) VALUES ('2', '1');
INSERT INTO security_resource_role (resource_id,role_id) VALUES ('1', '2');
INSERT INTO security_resource_role (resource_id,role_id) VALUES ('2', '3');
INSERT INTO security_resource_role (resource_id,role_id) VALUES ('3', '3');

vo&&dao

Class

public class SecurityResource{
    private int id;
    private String name;
    private String type;
    private String resource;
    private String description;
}

@Repository
public class SecurityResourcesDao implements ISecurityResourcesDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    private static final Log log = LogFactory.getLog(SecurityUserDao.class);
    @Override
    public List<SecurityResource> findAll() {
        try {
            List<SecurityResource> resourceList = new ArrayList<SecurityResource>();
            List<Map<String, Object>> resources = jdbcTemplate
                    .queryForList("select * from security_resource");

            for (Map<String, Object> map : resources) {
                SecurityResource r = new SecurityResource();
                r.setId(Integer.valueOf(map.get("id").toString()));
                r.setName(map.get("name").toString());
                r.setType(map.get("type").toString());
                r.setResource(map.get("resource").toString());
                r.setDescription(map.get("description").toString());
                resourceList.add(r);
            }

            return resourceList;
        } catch (RuntimeException re) {
            log.error("find all resource failed " + re);
            throw re;
        }
    }

    // 加载资源与对应的权限
    /**
     *   ConfigAttribute interface
     *   Class SecurityConfig  -> attribute : role
     */
    @Override
    public Collection<ConfigAttribute> loadRoleByResource(String url) {
        try {
            String sql = "select r.name as role,re.resource as url "
                    + "from security_role r join security_resource_role rr on r.id=rr.role_id "
                    + "join security_resource re on re.id=rr.resource_id "
                    + "where re.resource='" + url + "'";
            List<Map<String, Object>> authList = jdbcTemplate.queryForList(sql);
            Collection<ConfigAttribute> auths = new ArrayList<ConfigAttribute>();

            for(Map<String, Object> map:authList){
                ConfigAttribute auth = new SecurityConfig(map.get("role").toString());
                auths.add(auth);
            }

            return auths;
        } catch (RuntimeException re) {
            log.error("find roles by url failed " + re);
            throw re;
        }
    }

    private final String INSERT_ROLE_RESOURCES_SQL =
            "INSERT security_resource_role(resource_id,role_id)" +
            "VALUES(?,?)";
    @Override
    public int addResourcesToRole(SecurityRole role, SecurityResource resource) {
        return jdbcTemplate.update( INSERT_ROLE_RESOURCES_SQL,resource.getId(),role.getId());
    }

    @Override
    public List<SecurityResource> getResourceByRole(SecurityRole role) {
        String sql = "SELECT res.name,res.description,res.id ,res.resource,res.type FROM security_role r "
                + "JOIN security_resource_role rr ON r.id ='" + role.getId() +"' AND r.id = rr.role_id "
                + "JOIN security_resource res on rr.resource_id = res.id";
        RowMapper<SecurityResource> mapper = new RowMapper<SecurityResource>() {
            public SecurityResource mapRow(ResultSet rs, int rowNum) throws SQLException {
                SecurityResource resource = new SecurityResource();
                resource.setId((int) rs.getLong("id"));
                resource.setName(rs.getString("name"));
                resource.setType(rs.getString("type"));
                resource.setResource(rs.getString("resource"));
                resource.setDescription(rs.getString("description"));
                return resource;
            }
        };

        List<SecurityResource> result = jdbcTemplate.query(sql, mapper);
        log.info(result);
        return result;
    }

} 

配置DAO-spring-sample-security.xml

<context:component-scan base-package="security.dao.impl" />

项目改善

经过上述配置,验证流程已经可以完成,但是只有拥有ROLE_TEST的用户可以成功登陆
原因是登陆成功处理器和登陆失败处理器的url被拦截了 ,
url为 test/mytest/loginSuccess
所以要对登陆成功失败处理器的url进行修改

附配置文件-spring-sample-security.xml

<beans:beans xmlns="http://www.springframework.org/schema/security"
    xmlns:beans="http://www.springframework.org/schema/beans"
    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.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context.xsd">  

    <!--配置  security 管理 控制器-->
    <context:component-scan base-package="security" />
    <context:component-scan base-package="security.service.impl" />
    <context:component-scan base-package="security.dao.impl" />
    <!-- 去除不需要拦截的url -->
    <http pattern="/libs/**" security="none"/>
    <http pattern="/login.html" security="none" />
    <http pattern="/resources/**" security="none" />
    <http pattern="/test/mytest/loginSuccess" security="none" />

    <!-- 配置一层拦截,需要输入正确用户名密码才能访问网站 -->
    <http auto-config="true" use-expressions="true" >
        <!-- 拦截所有不是ROLE_USER的请求  -->
        <intercept-url pattern="/*" access="hasAnyRole('ROLE_ADMIN','ROLE_DEV','ROLE_TEST')" />

        <!-- 登录配置 -->
        <form-login login-page="/login.html"
            default-target-url="/test/mytest/loginSuccess"
            authentication-failure-url="/test/mytest/loginFaild"/>  

        <logout invalidate-session="true"
            logout-success-url="/"
            logout-url="/j_spring_security_logout"/>
        <custom-filter ref="appFilter" before="FILTER_SECURITY_INTERCEPTOR" />
    </http> 

    <beans:bean id="appFilter"
        class="security.filter.AppFilterSecurityInterceptor">
        <beans:property name="authenticationManager" ref="authenticationManager" />
        <beans:property name="accessDecisionManager" ref="appAccessDecisionManagerBean" />
        <beans:property name="securityMetadataSource" ref="appSecurityMetadataSource" />
    </beans:bean>

    <!--默认拦截器  -->
    <authentication-manager alias="authenticationManager">
    <!--验证配置,认证管理器,实现用户认证的入口,主要实现UserDetailsService接口即可 -->
      <authentication-provider user-service-ref="appUserDetailService">
           <password-encoder ref="passwordEncoder">
                <salt-source ref="saltSource"/>
           </password-encoder>
       </authentication-provider>
    </authentication-manager>

     <!--在这个类中,你就可以从数据库中读入用户的密码,角色信息,是否锁定,账号是否过期等 -->
    <beans:bean id="appUserDetailService" class="security.service.impl.AppUserDetailService" />
    <!--访问决策器,决定某个用户具有的角色,是否有足够的权限去访问某个资源 -->
    <beans:bean id="appAccessDecisionManagerBean"
        class="security.filter.properties.AppAccessDecisionManager">
    </beans:bean>
    <!--资源源数据定义,将所有的资源和权限对应关系建立起来,即定义某一资源可以被哪些角色访问-->
    <beans:bean id="appSecurityMetadataSource"
        class="security.filter.properties.AppInvocationSecurityMetadataSource" >
        <beans:constructor-arg name="resourcesDao" ref="securityResourcesDao"/>
    </beans:bean>
     <!--加密密码  -->
    <beans:bean id="passwordEncoder" class="org.springframework.security.authentication.encoding.ShaPasswordEncoder" />

    <!--为密码添加 saltSource -->
    <beans:bean id="saltSource" class="org.springframework.security.authentication.dao.ReflectionSaltSource" >
        <beans:property name="userPropertyToUse" value="salt"/>
    </beans:bean>

</beans:beans>  

Spring MVC 项目搭建 -6- spring security 使用自定义Filter实现验证扩展资源验证,使用数据库进行配置的更多相关文章

  1. Spring MVC 项目搭建 -5- spring security 使用数据库进行验证

    Spring MVC 项目搭建 -5- spring security 使用数据库进行验证 1.创建数据表格(这里使用的是mysql) CREATE TABLE security_role ( id ...

  2. Spring MVC 项目搭建 -3- 快速 添加 spring security

    Spring MVC 项目搭建 -3- 快速 添加 spring security 1.添加 spring-sample-security.xml <!-- 简单的安全检验实现 --> & ...

  3. Spring MVC 项目搭建 -4- spring security-添加自定义登录页面

    Spring MVC 项目搭建 -4- spring security-添加自定义登录页面 修改配置文件 <!--spring-sample-security.xml--> <!-- ...

  4. Spring MVC 项目搭建 -2- 添加service ,dao,junit

    Spring MVC 项目搭建 -2- 添加service ,dao,junit 1.dao public class Hero { private String name; public Strin ...

  5. Spring MVC 项目搭建 -1- 创建项目

    Spring MVC 项目搭建 -1- 创建项目 1.创建 Dynamic Web project (SpringDemo),添加相关jar包 2.创建一个简单的controller类 package ...

  6. IDEA 创建Spring MVC项目搭建

    概述 IntelliJ IDEA是一款更加集成智能的开发工具,相对Myeclipse开发而言,使用起来相对更加的方便:初步手动使用IDEA搭建Spring MVC项目,现将操作流程整理记录如下. 环境 ...

  7. 简单Spring MVC项目搭建

    1.新建Project 开发环境我使用的是IDEA,其实使用什么都是大同小异的,关键是自己用的顺手. 首先,左上角File→New→Project.在Project页面选择Maven,然后勾上图中所示 ...

  8. Java Spring MVC项目搭建(二)——项目配置

    1.站点配置文件web.xml 每一个Spring MVC 项目都必须有一个站点配置文件web.xml,他的主要功能吗....有一位大哥已经整理的很好,我借来了,大家看看: 引用博客地址: http: ...

  9. Java Spring MVC项目搭建(一)——Spring MVC框架集成

    1.Java JDK及Tomcat安装 我这里安装的是JDK 1.8 及 Tomcat 8,安装步骤详见:http://www.cnblogs.com/eczhou/p/6285248.html 2. ...

随机推荐

  1. JAVA 编程规范

       软件开发技术规范 PTHINK-DEVELOP-JAVA-091010         Java语言编程规范   2009-10-10发布                2009-10-11实施 ...

  2. bootstrap学习笔记之导航条基础

    导航条基础 导航条(navbar)和上一节介绍的导航(nav),就相差一个字,多了一个"条"字.其实在Bootstrap框架中他们还是明显的区别.在导航条(navbar)中有一个背 ...

  3. Android 桌面不显示应用图标

    忽然有一天,运行自己的程序,发现桌面没有应用图标了. google了半天,也没什么发现. 最后发现是主Activity中: <action android:name="android. ...

  4. 10.Java 加解密技术系列之 DH

    Java 加解密技术系列之 DH 序 概念 原理 代码实现 结果 结束语 序 上一篇文章中简单的介绍了一种非对称加密算法 — — RSA,今天这篇文章,继续介绍另一种非对称加密算法 — — DH.当然 ...

  5. JQuery版评分控件

    Hi All, 分享一个学习JQuery做的一个评分控件. 需求:当鼠标移动到 ‘☆’ 上时,该字符左边的 ‘☆’ 变成 '★',该字符右边仍然是 ‘☆’, 并显示相应星星数的评价结果:当鼠标推出 ‘ ...

  6. 利刃 MVVMLight

    已经很久没有写系列文章了,上一次是2012年写的HTLM5系列,想想我们应该是较早一批使用HTML5做项目的人. 相比我当时动不动100+的粉丝增长和两天3000+的阅读量,MVVM Light只能算 ...

  7. C语言之运算符和条件结构

    表达式:是有操作数和运算符组成的. 操作数:常量.变量.子表达式 X=(x+2)*(y-2); 运算符: 赋值运算符:= .其作用是做赋值运算,将等号后边的值赋值给等号前边的. 复合赋值运算符: += ...

  8. CI Weekly #20 | 从持续集成的角度看 “云” 的价值

    很多移动开发工程师对 fastlane 耳熟能详,最近 flow.ci 的 iOS 工作流「编译」这步已采用 fastlane gym 工具(iOS 应用打包签名自动化),进一步优化了构建打包速度.快 ...

  9. C. Friends

    C. Friends 题意 对于任一点,求到此点距离不超过6的节点数. 分析 第一次dfs,形成一个以 1 为根的有向树,设 down[i][j] 为以i为根节点,距离 i 点距离不超过 j 的节点数 ...

  10. 《孵化twitter》读书笔记

    寒假在家看完了第一本书——<孵化twitter:从蛮荒到IPO的狂野旅程>.在2014上半年,尝试看了该书的英文原版,奈何自己英语太菜,实在看不懂.这次有了中文版,就决定要看看.该书对于我 ...