spring-security实现权限管理

来源:互联网 发布:淘宝双11红包怎么领 编辑:程序博客网 时间:2024/05/22 10:45
首先需要配置好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 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 余利宝无法开通怎么办 qq群视频进不去怎么办 qq空间状态异常怎么办 手机qq空间异常怎么办 手机被恶意骚扰怎么办 织布机开不起来怎么办 座机键盘锁了怎么办 邮箱登录锁死怎么办 标志408油耗高怎么办 gucci白鞋掉皮怎么办 深圳摇不到车牌怎么办 车辆换牌照需要怎么办 商标申请有异议怎么办 唯品会账号被冻结怎么办 易直播回放收费怎么办 手机屏幕被摔了怎么办 xp调分辨率黑屏怎么办 公司logo被盗用怎么办 商标注册证掉了怎么办 商标注册证书丢了怎么办 市场监督管理局罚款怎么办 东莞居住证掉了怎么办 工商注销了税务怎么办 楼道自来水爆了怎么办 工商证没年检怎么办 工行信用卡被锁怎么办 外地卡密码锁了怎么办 营业执照年审过期了怎么办 工商营业执照吊销了怎么办 小规模企业工商年检怎么办 血流变检查偏高怎么办 信誉卡没有邮箱怎么办 税务年报没报怎么办 工商忘记年审了怎么办 营业执照脱审了怎么办 公司年审没有弄怎么办 车检标志丢了怎么办 机动车年检丢了怎么办 汽车保险标志丢了怎么办 车辆年检贴丢失怎么办 车检标贴丢了怎么办