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,然后页面上只展示这些标签。但是此项目在加权限管理的时候第一版已经开发完成,用删除没权限标签的方式更方便实现。)
- Spring Security实现权限管理
- Spring security实现权限管理
- spring-security实现权限管理
- Spring security实现权限管理
- Spring security实现权限管理
- Spring security实现权限管理
- Spring security实现权限管理
- Spring security实现权限管理
- Spring security实现权限管理
- Spring security实现权限管理
- 使用Spring Security实现权限管理
- 使用Spring Security实现权限管理
- 使用Spring Security实现权限管理
- 使用Spring Security实现权限管理
- 使用Spring Security实现权限管理
- 使用Spring Security实现权限管理
- 使用Spring Security实现权限管理
- 使用Spring Security实现权限管理
- Unable to locate an executable at “/usr/bin/java/bin/java” (-1)
- jsp 总结
- oracle如何修改字段?
- 字符编码、字符存储、字符转换及工程中字符的使用
- 项目启动,无法加载Spring xsd文件
- spring-security实现权限管理
- 各个排序算法的总结
- Appium+Python+Django手机app自动化方案实现以及环境搭建
- PHP5中PDO的简单使用
- 过程
- 【在线lca 模板】LCA 练习题2 : hdu 3078 + poj2763 我要记一辈子的s b错误
- Linux下tomcat问题1
- Windows 多线程学习一
- wifi相关