1、权限配置在数据库中,典型的五张表。
1)t_user 用户表
2)t_role 角色表
3)t_user_role 用户-角色关联表
4)t_resource 资源表
5)t_role_resource 角色-资源关联表
2、建表语句
DROP TABLE IF EXISTS `t_resource`; CREATE TABLE `t_resource` ( `id` int(11) NOT NULL AUTO_INCREMENT , `name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL , `url` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL , PRIMARY KEY (`id`) ) DROP TABLE IF EXISTS `t_role`; CREATE TABLE `t_role` ( `id` int(11) NOT NULL AUTO_INCREMENT , `name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL , PRIMARY KEY (`id`) ) DROP TABLE IF EXISTS `t_role_resource`; CREATE TABLE `t_role_resource` ( `id` int(11) NOT NULL AUTO_INCREMENT , `role_id` int(11) NULL DEFAULT NULL , `resource_id` int(11) NULL DEFAULT NULL , PRIMARY KEY (`id`) ) CREATE TABLE `t_user` ( `id` int(11) NOT NULL AUTO_INCREMENT , `account` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL , `password` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL , PRIMARY KEY (`id`) ) DROP TABLE IF EXISTS `t_user_role`; CREATE TABLE `t_user_role` ( `id` int(11) NOT NULL AUTO_INCREMENT , `user_id` int(11) NULL DEFAULT NULL , `role_id` int(11) NULL DEFAULT NULL , PRIMARY KEY (`id`) )
3、对应的领域实体
1)用户
package cn.luxh.app.domain; /** * 用户 * @author Luxh */ public class User { private Integer id; /**帐号*/ private String account; /**密码*/ private String password; @Override public int hashCode() { return account.hashCode(); } @Override public boolean equals(Object obj) { User user = (User) obj; return this.account.equals(user.getAccount()); } //getter setter //... }
2)角色
package cn.luxh.app.domain; /** * 角色 * @author Luxh */ public class Role { private Integer id; /**角色名称*/ private String name; //getter setter //... }
3)用户-角色
package cn.luxh.app.domain; /** * 用户角色 * @author Luxh */ public class UserRole { private Integer id; /**用户id*/ private Integer userId; /**角色id*/ private Integer roleId; //getter setter //... }
4)资源
package cn.luxh.app.domain; /** * 资源 * @author Luxh */ public class Resource { private Integer id; /**资源名称*/ private String name; /**访问地址*/ private String url; //getter setter }
5)角色-资源
package cn.luxh.app.domain; /** * 角色资源 * @author Luxh */ public class RoleResource { private Integer id; /**角色id*/ private Integer roleId; /**资源id*/ private Integer resourceId; //getter setter }
4、配置文件
在web.xml文件中加上如下内容:
<!-- SpringSecurity权限框架 --> <filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class> org.springframework.web.filter.DelegatingFilterProxy </filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 获取Spring Security session的生命周期--> <listener> <listener-class> org.springframework.security.web.session.HttpSessionEventPublisher </listener-class> </listener>
当然配置spring监听器的时候得把springsecurity的权限配置文件给加载进去:
<!-- 配置Spring监听器 --> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml,classpath:application-security.xml</param-value> </context-param>
权限配置文件内容如下:
<?xml version="1.0" encoding="UTF-8"?> <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.2.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd"> <http pattern="/login" security="none" /> <http pattern="/resources/**" security="none" /> <http auto-config="true" use-expressions="true" access-denied-page="/denied"> <!-- default-target-url 指定了从登录页面登录后进行跳转的页面 always-use-default-target true表示登录成功后强制跳转 authentication-failure-url 表示验证失败后进入的页面 login-processing-url 设置验证登录验证地址,如果不设置,默认是j_spring_security_check username-parameter,password-parameter 设置登录用户名和密码的请求name,默认:j_username,j_password default-target-url="/user/home" --> <form-login login-page="/login" always-use-default-target="true" authentication-failure-url="/login?error=1" authentication-success-handler-ref="successHandler" /> <logout logout-success-url="/login" /> <!-- error-if-maximum-exceeded 后登陆的账号会挤掉第一次登陆的账号 session-fixation-protection 防止伪造sessionid攻击. 用户登录成功后会销毁用户当前的session. 创建新的session,并把用户信息复制到新session中. --> <session-management invalid-session-url="/login?error=3" session-fixation-protection="none"> <concurrency-control max-sessions="1" error-if-maximum-exceeded="true" expired-url="/login?error=2" /><!-- 阻止第二次登录 --> </session-management> <custom-filter ref="appInterceptor" before="FILTER_SECURITY_INTERCEPTOR"/> </http> <authentication-manager alias="appAuthenticationManager"> <authentication-provider user-service-ref="userDetailsService"> </authentication-provider> </authentication-manager> <beans:bean id="appInterceptor" class="cn.luxh.app.security.AppSecurityInterceptor"> <beans:property name="authenticationManager" ref="appAuthenticationManager"/> <beans:property name="accessDecisionManager" ref="appAccessDescisionManager"/> <beans:property name="securityMetadataSource" ref="appSecurityMetadataSource"/> </beans:bean> <beans:bean id="userDetailsService" class="cn.luxh.app.security.UserDetailsServiceImpl" /> <beans:bean id="appSecurityMetadataSource" class="cn.luxh.app.security.AppSecurityMetadataSource"> <beans:constructor-arg name="roleService" ref="roleService"></beans:constructor-arg> <beans:constructor-arg name="resourceService" ref="resourceService"></beans:constructor-arg> </beans:bean> <beans:bean id="appAccessDescisionManager" class="cn.luxh.app.security.AppAccessDescisionManager"/> <beans:bean id="roleService" class="cn.luxh.app.service.RoleServiceImpl"/> <beans:bean id="resourceService" class="cn.luxh.app.service.ResourceServiceImpl"/> <!-- 登录成功业务处理 --> <beans:bean id="successHandler" class="cn.luxh.app.security.LoginAuthenticationSuccessHandler"> <beans:property name="url" value="/index"></beans:property> </beans:bean> </beans:beans>
5、权限配置文件中的关键类
1)UserDetailsServiceImpl
package cn.luxh.app.security; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.GrantedAuthority; 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 cn.luxh.app.domain.Role; import cn.luxh.app.domain.User; import cn.luxh.app.exception.UserException; import cn.luxh.app.persistence.RoleMapper; import cn.luxh.app.persistence.UserMapper; public class UserDetailsServiceImpl implements UserDetailsService{ private static Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class); @Autowired private UserMapper userMapper; @Autowired private RoleMapper roleMapper; /** * @param account 登录帐号 */ public UserDetails loadUserByUsername(String account) throws UsernameNotFoundException { log.info("登录账号:"+account); org.springframework.security.core.userdetails.User userDetails = null; try { User user = userMapper.selectByAccount(account); if(user == null) { throw new UserException("帐号:"+account+" 不存在!"); } Collection<GrantedAuthority> grantedAuthorities = getGrantedAuthorities(user); boolean enables = true; boolean accountNonExpired = true; boolean credentialsNonExpired = true; boolean accountNonLocked = true; userDetails = new org.springframework.security.core.userdetails.User(user.getAccount(), user.getPassword(), enables, accountNonExpired, credentialsNonExpired, accountNonLocked, grantedAuthorities); }catch(Exception e) { log.error(e.getMessage()); e.printStackTrace(); } return userDetails; } /** * 根据用户获取该用户拥有的角色 * @param user * @return */ private Set<GrantedAuthority> getGrantedAuthorities(User user) { Set<GrantedAuthority> grantedAuthorities = new HashSet<GrantedAuthority>(); List<Role> roles = roleMapper.selectByUserId(user.getId()); if(roles != null) { for(Role role : roles) { grantedAuthorities.add(new SimpleGrantedAuthority(role.getName())); } } return grantedAuthorities; } }
2)AppSecurityInterceptor
View Code
package cn.luxh.app.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; /** * 自定义拦截器 * @author Luxh */ public class AppSecurityInterceptor extends AbstractSecurityInterceptor implements Filter{ private static Logger log = LoggerFactory.getLogger(AppSecurityInterceptor.class); private FilterInvocationSecurityMetadataSource securityMetadataSource; @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException { log.info("开始拦截......"); FilterInvocation fi = new FilterInvocation(request, response, filterChain); InterceptorStatusToken token = super.beforeInvocation(fi); try { fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); } catch (Exception e) { e.printStackTrace(); }finally{ super.afterInvocation(token,null); } } @Override public Class<?> getSecureObjectClass() { return FilterInvocation.class; } @Override public SecurityMetadataSource obtainSecurityMetadataSource() { return securityMetadataSource; } @Override public void init(FilterConfig arg0) throws ServletException { } @Override public void destroy() { } public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() { return securityMetadataSource; } public void setSecurityMetadataSource( FilterInvocationSecurityMetadataSource securityMetadataSource) { this.securityMetadataSource = securityMetadataSource; } }
3)AppAccessDescisionManager
View Code
package cn.luxh.app.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; /** * * 访问决策器,决定某个用户具有的角色,是否有足够的权限去访问某个资源 ;做最终的访问控制决定 . * @author Luxh * */ public class AppAccessDescisionManager implements AccessDecisionManager { private static Logger log = LoggerFactory.getLogger(AppAccessDescisionManager.class); /** * 认证用户是否具有权限访问该url地址 */ @Override public void decide(Authentication authentication, Object obj, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException { log.info("AppAccessDescisionManager 验证用户是否具有访问资源的权限"); if(configAttributes != null) { Iterator<ConfigAttribute> it = configAttributes.iterator(); while(it.hasNext()) { //访问资源需要的权限 String needRole = it.next().getAttribute(); //authentication.getAuthorities() 用户所有的权限 for(GrantedAuthority ga:authentication.getAuthorities()){ if(needRole.equals(ga.getAuthority())){ return; } } } } //没有权限 throw new AccessDeniedException("没有权限访问!"); } /** * 启动时候被AbstractSecurityInterceptor调用,决定AccessDecisionManager是否可以执行传递ConfigAttribute。 */ @Override public boolean supports(ConfigAttribute arg0) { return true; } /** * 被安全拦截器实现调用,包含安全拦截器将显示的AccessDecisionManager支持安全对象的类型 */ @Override public boolean supports(Class<?> arg0) { return true; } }
4)AppSecurityMetadataSource
View Code
package cn.luxh.app.security; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; 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.luxh.app.domain.Resource; import cn.luxh.app.domain.Role; import cn.luxh.app.service.ResourceService; import cn.luxh.app.service.RoleService; /** * 从数据库中查询出资源和权限(角色),并将它们的关系对应起来 * @author Luxh * */ public class AppSecurityMetadataSource implements FilterInvocationSecurityMetadataSource { private static Logger log = LoggerFactory.getLogger(AppSecurityMetadataSource.class); private RoleService roleService; private ResourceService resourceService; public RoleService getRoleService() { return roleService; } public void setRoleService(RoleService roleService) { this.roleService = roleService; } public ResourceService getResourceService() { return resourceService; } public void setResourceService(ResourceService resourceService) { this.resourceService = resourceService; } private AntPathMatcher urlMatcher = new AntPathMatcher(); /* 保存资源和权限的对应关系 key-资源url value-权限 */ private Map<String,Collection<ConfigAttribute>> relationMap = null; public AppSecurityMetadataSource(RoleService roleService,ResourceService resourceService) { log.info("执行 AppSecurityMetadataSource 构造方法"); this.roleService = roleService; this.resourceService = resourceService; loadResourceAndRoleRelation(); } @Override public Collection<ConfigAttribute> getAllConfigAttributes() { return null; } /** * 根据请求的url,获取访问该url所需的权限(角色) */ @Override public Collection<ConfigAttribute> getAttributes(Object obj) throws IllegalArgumentException { //获取请求的url地址 String requestUrl = ((FilterInvocation)obj).getRequestUrl(); log.info("请求的 requestUrl :"+requestUrl); Iterator<String> it = relationMap.keySet().iterator(); while(it.hasNext()) { String url = it.next(); log.info("配置的 url :"+url); if(requestUrl.indexOf("?")!=-1) { requestUrl = requestUrl.substring(0, url.indexOf("?")); } log.info("hey man :"+url); if(urlMatcher.match(url, requestUrl)) { log.info("已匹配 :"+url); return relationMap.get(url); } } return null; } @Override public boolean supports(Class<?> arg0) { return true; } /** * 加载资源和角色的对应关系 */ private void loadResourceAndRoleRelation() { relationMap = new HashMap<String,Collection<ConfigAttribute>>(); //查出所有角色 List<Role> roles = roleService.getAll(); if(roles != null) { for(Role role : roles) { //查出某个角色可以访问的资源 List<Resource> resources = resourceService.getByRoleId(role.getId()); if(resources != null) { for(Resource resource : resources) { Collection<ConfigAttribute> configAttributes = null; ConfigAttribute configAttribute = new SecurityConfig(role.getName()); if(relationMap.containsKey(resource.getUrl())){ configAttributes = relationMap.get(resource.getUrl()); configAttributes.add(configAttribute); }else{ configAttributes = new ArrayList<ConfigAttribute>() ; configAttributes.add(configAttribute); relationMap.put(resource.getUrl(), configAttributes); } } } } } } }
5)LoginAuthenticationSuccessHandler
View Code
package cn.luxh.app.security; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; /** * 登录验证成功处理器 * @author Luxh */ public class LoginAuthenticationSuccessHandler implements AuthenticationSuccessHandler{ private static Logger log = LoggerFactory.getLogger(LoginAuthenticationSuccessHandler.class); //登录验证成功后需要跳转的url private String url; public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { log.info("登录验证成功:"+request.getContextPath()+url); //response.sendRedirect(request.getContextPath()+url); request.getRequestDispatcher(url).forward(request, response); } public void setUrl(String url) { this.url = url; } }
6、其中资源表内容如下
7、源码,含数据库文件和初始化值
https://files.cnblogs.com/luxh/app4.rar
8、在页面上的控制权限
1)引入标签: <%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
2)使用标签:如下,审核操作时具有"ROLE_ADM"权限才可以看到。
<sec:authorize ifAnyGranted="ROLE_ADM"> <li><a href="javascript:auditPage('${ctx}/task/auditPage?taskId=${task.taskId}')">审核</a></li> </sec:authorize>