SpringSecurity 3.2入门(9)自定义权限控制代码实现

1、 一个自定义的filter,必须包含authenticationManager,accessDecisionManager,securityMetadataSource三个属性,我们的所有控制将在这三个类中实现 。

package cn.jxufe.core.security;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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;

public class MySecurityFilter extends AbstractSecurityInterceptor implements
        Filter {
    
    protected Logger logger = LoggerFactory.getLogger(getClass());
    
    // 与applicationContext-security.xml里的myFilter的属性securityMetadataSource对应,
    // 其他的两个组件,已经在AbstractSecurityInterceptor定义
    private FilterInvocationSecurityMetadataSource securityMetadataSource;

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

    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        logger.debug("------------MyFilterSecurityInterceptor.doFilter()-----------开始拦截了....");  
        FilterInvocation fi = new FilterInvocation(request, response, chain);
        invoke(fi);
    }

    private void invoke(FilterInvocation fi) throws IOException,
            ServletException {
        // object为FilterInvocation对象
        // 1.获取请求资源的权限 
        // 执行Collection<ConfigAttribute> attributes =
        // SecurityMetadataSource.getAttributes(object);

        logger.debug("--------------用户发送请求--------------");  
        InterceptorStatusToken token = null;
        token = super.beforeInvocation(fi);
        try {
            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
        }catch (Exception e) {
        }finally {
            super.afterInvocation(token, null);
        }
        
        logger.debug("------------MyFilterSecurityInterceptor.doFilter()-----------拦截结束了....");  
    }

    public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {
        return securityMetadataSource;
    }

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

    public void init(FilterConfig arg0) throws ServletException {
        // TODO Auto-generated method stub
    }

    public void destroy() {
        // TODO Auto-generated method stub
    }

    public Class<? extends Object> getSecureObjectClass() {
        // 下面的MyAccessDecisionManager的supports方面必须放回true,否则会提醒类型错误
        return FilterInvocation.class;
    }
}
MySecurityFilter.java

2、用于启动时加载资源列表,还拥有判断是否拥有请求访问资源权限的方法。

package cn.jxufe.core.security;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.util.AntPathMatcher;

import cn.jxufe.core.dao.BaseDao;
import cn.jxufe.core.entiry.Resource;
import cn.jxufe.core.entiry.Role;

public class MyFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
    
    protected Logger logger = LoggerFactory.getLogger(getClass());
    
    private AntPathMatcher urlMatcher = new AntPathMatcher();  
    
    private static Map<String, Collection<ConfigAttribute>> resourceMap = null;
      
    private BaseDao baseDao;
    
    public BaseDao getBaseDao() {
        return baseDao;
    }

    public void setBaseDao(BaseDao baseDao) {
        this.baseDao = baseDao;
    }
    
    //由spring调用
    public MyFilterInvocationSecurityMetadataSource(BaseDao baseDao) {
        this.baseDao=baseDao;
        loadResourceDefine();
    }

    //加载所有资源
    private void loadResourceDefine() {
        logger.debug("容器启动(MySecurityMetadataSource:loadResourceDefine)");
        logger.debug("--------------开始加载系统资源与权限列表数据--------------");  
           resourceMap = new HashMap<String,Collection<ConfigAttribute>>(); 
           String sql="select * from t_system_resource_info";
                List<Resource> resources = this.baseDao.findListBeanByArray(sql, Resource.class);
                for(Resource resource : resources){
                    Collection<ConfigAttribute> configAttributes = null;  
                    ConfigAttribute configAttribute = new SecurityConfig(resource.getName());  
                    if(resourceMap.containsKey(resource.getPath())){  
                        configAttributes = resourceMap.get(resource.getPath());  
                        configAttributes.add(configAttribute);  
                    }else{  
                        configAttributes = new ArrayList<ConfigAttribute>() ;  
                        configAttributes.add(configAttribute);  
                        resourceMap.put(resource.getPath(), configAttributes);  
                    }  
                }  
                logger.debug("--------------系统资源与权限列表数据加载结束--------------");  
    }
    
    /**
     *  根据请求的资源地址,获取它所拥有的权限 
     */
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
         //获取请求的url地址  
        String requestUrl = ((FilterInvocation) object).getRequestUrl();
        logger.debug("--------------取得请求资源所需权限(MySecurityMetadataSource:getAttributes)--------------");
        logger.debug("--------------请求地址为:"+requestUrl+"--------------");  
        Iterator<String> it = resourceMap.keySet().iterator();  
            while(it.hasNext()){  
                String _url = it.next();  
                if(_url.indexOf("?")!=-1){  
                    _url = _url.substring(0, _url.indexOf("?"));  
                }  
                if(urlMatcher.match(_url,requestUrl))  
                    return resourceMap.get(_url);  
            }
            return null; //如果是想做到没配的资源默认可以访问的话,那么就返回空或者NULL
            
            /**
             * 使用下面的写法代替return null;没配的资源则不可以访问,建议开发的时候还是用上面的为好。
             */
//            Collection<ConfigAttribute> returnCollection = new ArrayList<ConfigAttribute>();
//            returnCollection.add(new SecurityConfig("ROLE_NO_USER")); 
//            return returnCollection;
    }
    
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        Set<ConfigAttribute> allAttributes = new HashSet<ConfigAttribute>();
        for (Entry<String, Collection<ConfigAttribute>> entry : resourceMap.entrySet()) {
            allAttributes.addAll(entry.getValue());
        }
        logger.debug(allAttributes.toString());
        return allAttributes;
    }
    
    public Collection<ConfigAttribute> getConfigAttributes(String...value) {
        return SecurityConfig.createList(value);
    }
    
    public boolean supports(Class<?> clazz) {
        return true;
    }

}
MyFilterInvocationSecurityMetadataSource.java

3、访问决策器,决定某个用户具有的角色,是否有足够的权限去访问某个资源 ;做最终的访问控制决定。

package cn.jxufe.core.security;

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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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;

public class MyAccessDecisionManager implements AccessDecisionManager {
    
    protected Logger logger = LoggerFactory.getLogger(getClass());
    
    /** 
     *  认证用户是否具有权限访问该url地址 
     *  
     */  
    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
        logger.debug("--------------匹配用户拥有权限和请求权限(MyAccessDecisionManager:decide)--------------");  
        logger.debug("--------------验证用户是否具有一定的权限--------------");  
         if(configAttributes == null) {
            return;
        }
        //所请求的资源拥有的权限(一个资源对多个权限)
        Iterator<ConfigAttribute> iterator = configAttributes.iterator();
        while(iterator.hasNext()) {
            ConfigAttribute configAttribute = iterator.next();
            //访问所请求资源所需要的权限
            String needPermission = configAttribute.getAttribute();
            logger.debug("--------------访问所请求资源所需要的权限为 " + needPermission+"--------------");  
            //用户所拥有的权限authentication
            for(GrantedAuthority ga : authentication.getAuthorities()) {
                if(needPermission.equals(ga.getAuthority())) {
                    logger.debug("--------------权限验证通过--------------"); 
                    return;
                }
            }
        }
        //没有权限让我们去捕捉
        logger.debug("--------------权限验证未通过--------------"); 
        throw new AccessDeniedException(" 没有权限访问!");
    }

    /** 
     * 启动时候被AbstractSecurityInterceptor调用,决定AccessDecisionManager是否可以执行传递ConfigAttribute。 
     */  
    public boolean supports(ConfigAttribute attribute) {
//         System.out.println("MyAccessDescisionManager.supports()------------"+attribute.getAttribute());  
        return true;
    }

      /** 
     * 被安全拦截器实现调用,包含安全拦截器将显示的AccessDecisionManager支持安全对象的类型 
     */  
    public boolean supports(Class<?> clazz) {
        logger.debug("MyAccessDescisionManager.supports()--------------------------------");  
        return true;
    }
    
}
MyAccessDecisionManager.java

4、验证登录信息。

package cn.jxufe.core.security;


import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import cn.jxufe.core.dao.BaseDao;
import cn.jxufe.core.entiry.User;

public class MyUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    private static final String USERNAME = "username";
    private static final String PASSWORD = "password";
    private static final String KAPTCHA = "kaptcha";

    private BaseDao baseDao;

    public BaseDao getBaseDao() {
        return baseDao;
    }

    public void setBaseDao(BaseDao baseDao) {
        this.baseDao = baseDao;
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (!request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }

        String username = obtainUsername(request);
        String password = obtainPassword(request);
        String kaptcha = obtainKaptcha(request);
        logger.debug("--------------用户:" + username +"正在登录---"+kaptcha+"-----------");  
        // 验证码校验
        String kaptchaExpected = (String) request.getSession().getAttribute(com.google.code.kaptcha.Constants.KAPTCHA_SESSION_KEY);
        if(!kaptcha.equals(kaptchaExpected)){
            BadCredentialsException exception = new BadCredentialsException("验证码不匹配!");
            throw exception;
        }
        
        // 账号与密码校验
        username = username.trim();
        String sql="select * from t_system_user_info where username=? and password=?";
        Object[] args=new Object[]{ username, password };

        User users = (User) this.baseDao.findUniqueBeanByArray(sql, User.class, args);
        if (users == null || !users.getPassword().equals(password)) {
            BadCredentialsException exception = new BadCredentialsException("用户名或密码不匹配!");
            throw exception;
        }
        // 实现 Authentication
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
        // 允许子类设置详细属性
        setDetails(request, authRequest);
        // 运行UserDetailsService的loadUserByUsername 再次封装Authentication
        return this.getAuthenticationManager().authenticate(authRequest);
    }

    @Override
    protected String obtainUsername(HttpServletRequest request) {
        Object obj = request.getParameter(USERNAME);
        return null == obj ? "" : obj.toString();
    }

    @Override
    protected String obtainPassword(HttpServletRequest request) {
        Object obj = request.getParameter(PASSWORD);
        return null == obj ? "" : obj.toString();
    }
    
    protected String obtainKaptcha(HttpServletRequest request) {
        Object obj = request.getParameter(KAPTCHA);
        return null == obj ? "" : obj.toString();
    }
    
    @Override
    protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) {
        super.setDetails(request, authRequest);
    }
}
MyUsernamePasswordAuthenticationFilter.java

5、登录成功后,根据用户名,返回一个Userdetail。

package cn.jxufe.core.security;

import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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.stereotype.Component;


import cn.jxufe.core.dao.BaseDao;
import cn.jxufe.core.entiry.Resource;

@Component("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {

    protected Logger logger = LoggerFactory.getLogger(getClass());
    
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Autowired
    private BaseDao baseDao;
    
    //登录验证
    @Override
    public UserDetails loadUserByUsername(String username)
    throws UsernameNotFoundException, DataAccessException {
        logger.debug("------------验证并授权(MyUserDetailsServiceImpl:loadUserByUsername)------------");
        
        // 根据用户名获取帐户和权限信息
        String sql = "SELECT username,password,enabled,name rname " +
                "FROM t_system_user_info u,t_system_role_info r,t_system_user_role ur " +
                "WHERE u.id=ur.user_id AND r.id=ur.role_id AND username= ?";
        // 如果一个用户具有多个权限,连接查询会返回一个List
        List list =this.jdbcTemplate.queryForList(sql, new Object[] { username });

        // 取出帐户和权限信息填充到User中返回
        if (list == null || list.size() <= 0)
            // spring-security定义的异常
            throw new UsernameNotFoundException("用户不存在!");
        // 如果用户存在
        Map<String, Object> map = (Map<String, Object>) list.get(0);
        // 密码
        String password = (String) map.get("password");
        // 帐户是否可用
        boolean enabled = ((Integer) map.get("enabled") == 1);
        boolean accountNonExpired = true;
        boolean credentialsNonExpired = true;
        boolean accountNonLocked = true;
        // 帐户所具有的权限
        
        Set<GrantedAuthority> authSet = new HashSet<GrantedAuthority>();
        List<Resource> resources=this.getResourceByUsername(username);
        logger.debug("------------用户所拥有权限------------");
        for (int i=0;i<resources.size();i++) {
            GrantedAuthority authority = new SimpleGrantedAuthority(resources.get(i).getName()); 
            authSet.add(authority);
        }
        logger.debug("------------"+authSet.toString()+"------------");
        // spring-security提供的类
        User userdetail = new User(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authSet);
        return userdetail;

    }
    
    private List<Resource> getResourceByUsername(String username){  
        String sql ="SELECT * FROM t_system_resource_info WHERE id IN(" +
                "SELECT DISTINCT resource_id FROM t_system_authority_resource WHERE authority_id IN(" +
                "SELECT authority_id FROM t_system_role_authority WHERE role_id IN(" +
                "SELECT role_id FROM t_system_user_role WHERE user_id  =( " +
                "SELECT id FROM t_system_user_info WHERE username= ? ))))";
        List<Resource> list =this.baseDao.findListBeanByArray(sql, Resource.class, new Object[] { username });
        return list;  
    }
    

}
MyUserDetailsService.java

6、配置信息

<?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"
    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="/**/*.css" security="none" />
    <security:http pattern="/**/*.js" security="none" />
    <security:http pattern="/**/*.jpg" security="none" />
    <security:http pattern="/**/*.jpeg" security="none" />
    <security:http pattern="/**/*.gif" security="none" />
    <security:http pattern="/**/*.png" security="none" />
    <security:http pattern="/favicon.ico" security="none" />
    <!-- 不过滤验证码 -->
    <security:http pattern="/captcha-image.htm" security="none" />
    <!-- 不过滤登录页面 -->
    <security:http pattern="/login.htm" security="none" />
    <security:http pattern="/login.jsp" security="none" />
    <!-- 不过滤首页 -->
    <security:http pattern="/index.htm" security="none" />
    <security:http pattern="/index.jsp" security="none" />

    <!-- 配置SpringSecurity的http安全服务 -->
    <!-- 使用了 use-expressions="true 则 需使用hasRole('ROLE_USER')-->
    <!-- 配置了auto-config="true"loginFilter报错,如果你没有自定义的登录页面,它就会跳转到security默认的登录页面中。 -->
    <security:http access-denied-page="/accessDenied.jsp" entry-point-ref="authenticationProcessingFilterEntryPoint">
        <security:session-management>
            <security:concurrency-control
                max-sessions="1" />
        </security:session-management>

        <!-- 检测失效的sessionId,session超时时,定位到另外一个URL -->
        <security:session-management
            invalid-session-url="/sessionTimeOut.jsp" />

        <!-- 配置登出信息,指定退出系统后,跳转页面 -->
        <security:logout logout-url="/logout"
            logout-success-url="/login.htm" invalidate-session="true" />
        
        <!-- 认证和授权 -->
        <security:custom-filter ref="myLoginFilter" position="FORM_LOGIN_FILTER"  />
        <security:custom-filter ref="securityFilter" before="FILTER_SECURITY_INTERCEPTOR"/>
        
    </security:http>

    <!-- 认证管理器,配置SpringSecutiry的权限信息 -->
    <security:authentication-manager>
        <security:authentication-provider>
            <!-- 使用数据库中的用户名和密码 -->
            <security:jdbc-user-service
                data-source-ref="dataSource" />
        </security:authentication-provider>
    </security:authentication-manager>

    <!-- 验证配置 , 认证管理器,实现用户认证的入口,主要实现UserDetailsService接口即可 -->
    <security:authentication-manager alias="myAuthenticationManager">
        <!-- 使用自己数据库中的用户和角色表,获取用户拥有的权限 -->
        <security:authentication-provider
            user-service-ref="myUserDetailsServiceImpl" />
    </security:authentication-manager>
    
    <!-- 登录验证器 -->
    <bean id="myLoginFilter"
        class="cn.jxufe.core.security.MyUsernamePasswordAuthenticationFilter">
        <!-- 处理登录 -->
        <property name="filterProcessesUrl" value="/j_spring_security_check"></property>
        <property name="usernameParameter" value="username"></property>
        <property name="passwordParameter" value="password"></property>
        <property name="authenticationSuccessHandler" ref="loginLogAuthenticationSuccessHandler"></property>
        <property name="authenticationFailureHandler" ref="simpleUrlAuthenticationFailureHandler"></property>
        <property name="authenticationManager" ref="myAuthenticationManager"></property>
        <property name="baseDao" ref="baseDao"></property>
    </bean>
    
    <bean id="loginLogAuthenticationSuccessHandler"
        class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
        <property name="defaultTargetUrl" value="/index.jsp"></property>
    </bean>
    <bean id="simpleUrlAuthenticationFailureHandler"
        class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
        <property name="defaultFailureUrl" value="/login.jsp"></property>
    </bean>
    
    
     <!-- 认证过滤器 -->
    <bean id="securityFilter" class="cn.jxufe.core.security.MySecurityFilter">
        <!-- 用户拥有的权限 -->
        <property name="authenticationManager" ref="myAuthenticationManager" />
        <!-- 用户是否拥有所请求资源的权限 -->
        <property name="accessDecisionManager" ref="myAccessDecisionManager" />
        <!-- 资源与权限对应关系 -->
        <property name="securityMetadataSource" ref="myFilterInvocationSecurityMetadataSource" />
    </bean>
    
    <bean id="myUserDetailsServiceImpl" class="cn.jxufe.core.security.MyUserDetailsService" />
    <bean id="myAccessDecisionManager" class="cn.jxufe.core.security.MyAccessDecisionManager"/>
    <bean id="myFilterInvocationSecurityMetadataSource" class="cn.jxufe.core.security.MyFilterInvocationSecurityMetadataSource">
        <constructor-arg name="baseDao" ref="baseDao" />    
    </bean>

    <bean id="baseDao" class="cn.jxufe.core.dao.BaseDaoImpl" />
    
    
    <!-- 定义上下文返回的消息的国际化 -->
    <bean id="messageSource"
        class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
        <property name="basename"
            value="classpath:org/springframework/seurity/messages_zh_CN" />
    </bean>

    <!-- 未登录的切入点 -->  
    <bean id="authenticationProcessingFilterEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">  
        <property name="loginFormUrl" value="/login.jsp"/>  
    </bean>
    
</beans>  
View Code

(详细的拦截过程将会在下一章中学习)。

原文地址:https://www.cnblogs.com/hehaiyang/p/4286988.html