spring-security实现权限管理

首先需要配置好spring-security:[spring-security简单配置](http://blog.csdn.net/qq_28890731/article/details/51013366)

整体思路是将权限管理分为两部分:资源控制和页面展示控制,首先确保没有权限的资源不可访问,其次是将没权限操作的页面标签隐藏。

数据库涉及到6张表,分别是:
1,用户表;2,角色表;3,资源(权限)表;
4,用户-角色关联表;5,角色-资源关联表;6,用户-资源关联表
目标:可以直接给用户关联资源;也可以将资源封装给角色,然后将角色关联给用户。

spring-security配置文件(下面有配置文件中相关的bean):

<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"
             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 pattern="/static/**" security="none"/>
    <http pattern="/login" security="none"/>
    <!--详细的安全校验规则,主要以登陆为主-->
    <http use-expressions="true" auto-config="true" entry-point-ref="authenticationProcessingFilterEntryPoint">
        <!--登陆相关,主要设置登陆页面的用户名和密码的属性名称,以及提交登陆的action名称-->
        <form-login login-page="/login" password-parameter="password" username-parameter="userName"
                    login-processing-url="/j_spring_security_check"
                    default-target-url="/index" always-use-default-target="true"
                    authentication-failure-handler-ref="aleiyeAuthenticationFailureHandler"
                    authentication-success-handler-ref="aleiyeAuthenticationSuccessHandler"/>
        <custom-filter ref="authenticationProcessingFilter" before="FILTER_SECURITY_INTERCEPTOR"/>
        <custom-filter ref="loginFilter" after="FORM_LOGIN_FILTER"/>
        <logout invalidate-session="true" logout-success-url="/login" logout-url="/j_spring_security_logout"/>
        <session-management invalid-session-url="/login" session-authentication-error-url="/login"/>
        <csrf disabled="true"/>
    </http>

    <beans:bean id="authenticationProcessingFilterEntryPoint"
                class="com.aleiye.web.system.security.filter.AleiyeAuthenticationEntryPoint">
        <beans:constructor-arg name="loginFormUrl" value="/login"/>
    </beans:bean>
    <!--登陆失败的处理类,可以添加错误信息-->
    <beans:bean id="aleiyeAuthenticationFailureHandler"
                class="com.aleiye.web.system.security.handler.AleiyeAuthenticationFailureHandler">
        <beans:constructor-arg name="defaultFailureUrl" value="/login"/>
    </beans:bean>
    <!--登陆成功的处理类,可以进行session的封装等-->
    <beans:bean id="aleiyeAuthenticationSuccessHandler"
                class="com.aleiye.web.system.security.handler.AleiyeAuthenticationSuccessHandler">
        <beans:constructor-arg name="defaultTargetUrl" value="/index"/>
    </beans:bean>
    <!-- 用户权限配置 -->
    <beans:bean id="authenticationProcessingFilter"
                class="com.aleiye.web.system.security.filter.AuthenticationProcessingFilter">
        <!-- 用户拥有的权限 -->
        <beans:property name="authenticationManager" ref="aleiyeAuthenticationManager"/>
        <!-- 用户是否拥有所请求资源的权限 -->
        <beans:property name="accessDecisionManager" ref="aleiyeAccessDecisionManager"/>
        <!-- 资源与权限对应关系 -->
        <beans:property name="securityMetadataSource" ref="aleiyeSecurityMetadataSource"/>
    </beans:bean>
    <!-- 校验是否登陆 -->
    <beans:bean id="loginFilter" class="com.aleiye.web.system.security.filter.AleiyeLoginFilter">
        <beans:constructor-arg name="loginUrl" value="/login"/>
        <beans:constructor-arg name="indexUrl" value="/index"/>
    </beans:bean>
    <!--校验用户权限-->
    <beans:bean id="aleiyeAccessDecisionManager"
                class="com.aleiye.web.system.security.filter.AleiyeAccessDecisionManager"/>
    <beans:bean id="aleiyeSecurityMetadataSource"
                class="com.aleiye.web.system.security.filter.AleiyeSecurityMetadataSource"/>
    <!--登陆用户信息查询-->
    <beans:bean id="daoUserProvider" class="com.aleiye.web.system.security.Provider.AleiyeAuthenticationProvider">
        <beans:property name="userDetailsService" ref="userDetailService" />
        <beans:property name="passwordEncoder" ref="md5PasswordEncoder" />
    </beans:bean>
    <!--权限校验管理类-->
    <authentication-manager alias="aleiyeAuthenticationManager">
        <authentication-provider ref="daoUserProvider" />
    </authentication-manager>
    <beans:bean id="md5PasswordEncoder"
                class="org.springframework.security.authentication.encoding.Md5PasswordEncoder"/>
    <beans:bean id="userDetailService" class="com.aleiye.web.system.security.service.UserDetailService"/>
</beans:beans>

第一部分:资源控制主要包括4个java类:
1:UserDetailService,AleiyeAuthenticationProvider封装用户拥有的权限
目的:将该用户能关联到的所有资源id集合赋值给用户实体

package com.aleiye.web.system.security.service;

import com.aleiye.client.service.security.model.RUserStatusEnum;
import com.aleiye.web.system.security.entity.AleiyeUserDetails;
import com.aleiye.web.system.security.entity.SysFeatureSource;
import com.aleiye.web.system.user.entity.SecUserInfo;
import com.aleiye.web.system.user.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

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

/**
 * @project aleiye-web
 * @auth wentao.yu
 * @Date 16/1/9PM4:19
 */
public class UserDetailService implements UserDetailsService {

    @Autowired
    private IUserService userService;
    @Autowired
    private ISysFeatureSourceService sysFeatureSourceService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        if (username != null && !username.isEmpty()) {
            SecUserInfo secUserInfo = userService.getUser(username);
            if (secUserInfo == null) {
                throw new UsernameNotFoundException("用户[" + username + "]不存在!");
            }
            //加载用户权限
            //此处特殊处理,对admin用户的权限为强制全部打开
            Collection<SimpleGrantedAuthority> grantedAuthorityCollection = new ArrayList<>();
            if (secUserInfo.getId() == 1) {
                List<SysFeatureSource> sourceList = sysFeatureSourceService.findAllEnableRecords();
                for (SysFeatureSource sysFeatureSource : sourceList) {
                    SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(sysFeatureSource.getId().toString());
                    grantedAuthorityCollection.add(simpleGrantedAuthority);
                }
            } else {
                //非admin用户加载相应权限
                List<SysFeatureSource> sources = sysFeatureSourceService.findSourceByUserId(secUserInfo.getId());
                for(SysFeatureSource source:sources){
                    SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(source.getId().toString());
                    grantedAuthorityCollection.add(simpleGrantedAuthority);
                }
            }
            AleiyeUserDetails ud = new AleiyeUserDetails(secUserInfo, secUserInfo.getStatus().intValue() == RUserStatusEnum.NORMAL.getValue(), grantedAuthorityCollection);
            return ud;
        }
        return null;
    }
}
package com.aleiye.web.system.security.Provider;

import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
import org.springframework.security.authentication.dao.SaltSource;
import org.springframework.security.authentication.encoding.PasswordEncoder;
import org.springframework.security.authentication.encoding.PlaintextPasswordEncoder;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.util.Assert;

/** 
* @author weiwentao: 
* @version 创建时间:2015年10月21日 下午5:47:03 
* 类说明 
*/
public class AleiyeAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
    // ~ Static fields/initializers
    // =====================================================================================

    /**
     * The plaintext password used to perform
     * {@link PasswordEncoder#isPasswordValid(String, String, Object)} on when the user is
     * not found to avoid SEC-2056.
     */
    private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";

    // ~ Instance fields
    // ================================================================================================

    private PasswordEncoder passwordEncoder;

    /**
     * The password used to perform
     * {@link PasswordEncoder#isPasswordValid(String, String, Object)} on when the user is
     * not found to avoid SEC-2056. This is necessary, because some
     * {@link PasswordEncoder} implementations will short circuit if the password is not
     * in a valid format.
     */
    private String userNotFoundEncodedPassword;

    private SaltSource saltSource;

    private UserDetailsService userDetailsService;

    public AleiyeAuthenticationProvider() {
        setPasswordEncoder(new PlaintextPasswordEncoder());
    }

    // ~ Methods
    // ========================================================================================================

    @SuppressWarnings("deprecation")
    protected void additionalAuthenticationChecks(UserDetails userDetails,
            UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {
        Object salt = null;

        if (this.saltSource != null) {
            salt = this.saltSource.getSalt(userDetails);
        }

        if (authentication.getCredentials() == null) {
            logger.debug("Authentication failed: no credentials provided");

            throw new BadCredentialsException(messages.getMessage(
                    "AbstractUserDetailsAuthenticationProvider.badCredentials",
                    "Bad credentials"));
        }

        String presentedPassword = authentication.getCredentials().toString();

        if (!passwordEncoder.isPasswordValid(userDetails.getPassword().toLowerCase(),
                presentedPassword, salt)) {
            logger.debug("Authentication failed: password does not match stored value");

            throw new BadCredentialsException(messages.getMessage(
                    "AbstractUserDetailsAuthenticationProvider.badCredentials",
                    "Bad credentials"));
        }
        //成功之后,封装session
        authentication.setDetails(userDetails);
    }

    protected void doAfterPropertiesSet() throws Exception {
        Assert.notNull(this.userDetailsService, "A UserDetailsService must be set");
    }

    protected final UserDetails retrieveUser(String username,
            UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {
        UserDetails loadedUser;

        try {
            loadedUser = this.getUserDetailsService().loadUserByUsername(username);
        }
        catch (UsernameNotFoundException notFound) {
            if (authentication.getCredentials() != null) {
                String presentedPassword = authentication.getCredentials().toString();
                passwordEncoder.isPasswordValid(userNotFoundEncodedPassword,
                        presentedPassword, null);
            }
            throw notFound;
        }
        catch (Exception repositoryProblem) {
            throw new InternalAuthenticationServiceException(
                    repositoryProblem.getMessage(), repositoryProblem);
        }

        if (loadedUser == null) {
            throw new InternalAuthenticationServiceException(
                    "UserDetailsService returned null, which is an interface contract violation");
        }
        return loadedUser;
    }

    /**
     * Sets the PasswordEncoder instance to be used to encode and validate passwords. If
     * not set, the password will be compared as plain text.
     * <p>
     * For systems which are already using salted password which are encoded with a
     * previous release, the encoder should be of type
     * {@code org.springframework.security.authentication.encoding.PasswordEncoder}.
     * Otherwise, the recommended approach is to use
     * {@code org.springframework.security.crypto.password.PasswordEncoder}.
     *
     * @param passwordEncoder must be an instance of one of the {@code PasswordEncoder}
     * types.
     */
    public void setPasswordEncoder(Object passwordEncoder) {
        Assert.notNull(passwordEncoder, "passwordEncoder cannot be null");

        if (passwordEncoder instanceof PasswordEncoder) {
            setPasswordEncoder((PasswordEncoder) passwordEncoder);
            return;
        }

        if (passwordEncoder instanceof org.springframework.security.crypto.password.PasswordEncoder) {
            final org.springframework.security.crypto.password.PasswordEncoder delegate = (org.springframework.security.crypto.password.PasswordEncoder) passwordEncoder;
            setPasswordEncoder(new PasswordEncoder() {
                public String encodePassword(String rawPass, Object salt) {
                    checkSalt(salt);
                    return delegate.encode(rawPass);
                }

                public boolean isPasswordValid(String encPass, String rawPass, Object salt) {
                    checkSalt(salt);
                    return delegate.matches(rawPass, encPass);
                }

                private void checkSalt(Object salt) {
                    Assert.isNull(salt,
                            "Salt value must be null when used with crypto module PasswordEncoder");
                }
            });

            return;
        }

        throw new IllegalArgumentException(
                "passwordEncoder must be a PasswordEncoder instance");
    }

    private void setPasswordEncoder(PasswordEncoder passwordEncoder) {
        Assert.notNull(passwordEncoder, "passwordEncoder cannot be null");

        this.userNotFoundEncodedPassword = passwordEncoder.encodePassword(
                USER_NOT_FOUND_PASSWORD, null);
        this.passwordEncoder = passwordEncoder;
    }

    protected PasswordEncoder getPasswordEncoder() {
        return passwordEncoder;
    }

    /**
     * The source of salts to use when decoding passwords. <code>null</code> is a valid
     * value, meaning the <code>DaoAuthenticationProvider</code> will present
     * <code>null</code> to the relevant <code>PasswordEncoder</code>.
     * <p>
     * Instead, it is recommended that you use an encoder which uses a random salt and
     * combines it with the password field. This is the default approach taken in the
     * {@code org.springframework.security.crypto.password} package.
     *
     * @param saltSource to use when attempting to decode passwords via the
     * <code>PasswordEncoder</code>
     */
    public void setSaltSource(SaltSource saltSource) {
        this.saltSource = saltSource;
    }

    protected SaltSource getSaltSource() {
        return saltSource;
    }

    public void setUserDetailsService(UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

    protected UserDetailsService getUserDetailsService() {
        return userDetailsService;
    }
}

2:AleiyeSecurityMetadataSource
1)系统启动时封装所有资源对应的权限;2)访问资源时获取该资源需要的权限
目的:用户访问某资源时 spring-security 会将url注入getAttributes方法,可通过正则关联到所有该url相对应的资源id集合,该方法返回资源id集合

package com.aleiye.web.system.security.filter;

import com.aleiye.web.system.security.entity.SysFeatureSource;
import com.aleiye.web.system.security.service.ISysFeatureSourceService;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
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 java.util.*;
import java.util.regex.Pattern;

/**
 * @author weiwentao:
 * @version 创建时间:2015年10月21日 下午12:26:29
 *          类说明
 */
public class AleiyeSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {

    private static final Logger _LOG = LoggerFactory.getLogger(AleiyeSecurityMetadataSource.class);

    @Autowired
    private ISysFeatureSourceService sysFeatureSourceService;

    private Map<Integer, Collection<ConfigAttribute>> resourceMap = new HashMap<>();

    private Map<Integer, Pattern> urlPatternMap = new HashMap<>();

    public AleiyeSecurityMetadataSource() {
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        List<SysFeatureSource> sourceList = sysFeatureSourceService.findAllEnableRecords();
        Collection<ConfigAttribute> collection = new ArrayList<>();
        for (SysFeatureSource sysFeatureSource : sourceList) {
            ConfigAttribute configAttribute = new SecurityConfig(sysFeatureSource.getId().toString());
            collection.add(configAttribute);
            //缓存访问路径与权限的映射关系
            Collection<ConfigAttribute> sourceConfig = new ArrayList<>();
            sourceConfig.add(configAttribute);
            resourceMap.put(sysFeatureSource.getId(), sourceConfig);
            //由于资源的路径不需要动态的变更,此处对正则进行缓存,以提高性能
            //只有当为非只读功能时,才需要进行正则过滤
            if (!sysFeatureSource.getReadOnly() && !sysFeatureSource.getUrlPattern().isEmpty()) {
                Pattern p = Pattern.compile(sysFeatureSource.getUrlPattern());
                urlPatternMap.put(sysFeatureSource.getId(), p);
            }else{
                _LOG.warn("the config of featureSource [id=" + sysFeatureSource.getId() + "] incorrect");
            }
        }
        return collection;
    }

    @Override
    public Collection<ConfigAttribute> getAttributes(Object arg0) throws IllegalArgumentException {
        //返回请求的资源需要的权限
        Collection<ConfigAttribute> configAttributes = new ArrayList<ConfigAttribute>();
        FilterInvocation fi = (FilterInvocation) arg0;
        for (Map.Entry<Integer, Pattern> entry : urlPatternMap.entrySet()) {
            if (entry.getValue().matcher(fi.getRequestUrl()).matches()) {
                configAttributes.addAll(resourceMap.get(entry.getKey()));
            }
        }
        return configAttributes;
    }

    @Override
    public boolean supports(Class<?> arg0) {
        // TODO Auto-generated method stub
        return true;
    }

}

3:AleiyeAccessDecisionManager 判断用户是否拥有所访问资源需要的权限
目的:将用户拥有的权限id集合与访问的资源所需权限id集合做对比,如果用户权限不满足所访问资源所需权限则抛出AccessDeniedException异常

package com.aleiye.web.system.security.filter;

import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;

import java.util.Collection;

/** 
* @author weiwentao: 
* @version 创建时间:2015年10月21日 下午12:24:57 
* 类说明 
*/
public class AleiyeAccessDecisionManager implements AccessDecisionManager {

    @Override
    public void decide(Authentication authentication, Object object,
            Collection<ConfigAttribute> configAttributes) throws AccessDeniedException,
            InsufficientAuthenticationException {
        //如果本系统没有需要授权的资源,则直接通过校验
        if(configAttributes == null){
            return;
        }
        for(ConfigAttribute conAtt:configAttributes){
            boolean flag = true;
            String needAuthKey=((SecurityConfig)conAtt).getAttribute();
            for(GrantedAuthority ga : authentication.getAuthorities()){
                if(needAuthKey.equals(ga.getAuthority())){
                    flag = false;
                    break ;
                }
            }
            if(flag)
                throw new AccessDeniedException("没有进行该操作的权限!");
        }
    }

    @Override
    public boolean supports(ConfigAttribute arg0) {
        return true;
    }

    @Override
    public boolean supports(Class<?> arg0) {
        return true;
    }

}

以上代码可实现资源访问控制。
但仅仅这样的话,页面还是会展示没有权限的资源链接,所以还需要将页面上没有权限访问的资源链接隐藏。

第二部分:页面展示控制
实现思路:页面标签上加一个class名,该名称与资源表中的资源相对应。这样用户所拥有的权限的补集就是所有不需要展示的页面标签class名。然后将不需要展示的class名放到session中,页面加载后将这些标签remove掉。
(更好的方式应该是拿到用户拥有的权限所对应的页面标签class,然后页面上只展示这些标签。但是此项目在加权限管理的时候第一版已经开发完成,用删除没权限标签的方式更方便实现。)

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值