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、源码,含数据库文件和初始化值
http://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>