Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
1、权限管理数据库的设计
权限管理包含五个表:user、role、user_role、resource、role_resource,它们的关系如下图:
Mysql建库的脚本如下:
CREATE DATABASE `test`;
USE `test`;
/*Table structure for table `resource` */
DROP TABLE IF EXISTS `resource`;
CREATE TABLE `resource` (
`resource_id` bigint(32) NOT NULL AUTO_INCREMENT,
`name` varchar(64) NOT NULL,
`url` varchar(256) NOT NULL,
PRIMARY KEY (`resource_id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
/*Data for the table `resource` */
insert into `resource`(`resource_id`,`name`,`url`) values (1,'common_index','/myPage/myPage.jsp'),(2,'admin_index','/admin/admin.jsp');
/*Table structure for table `role` */
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
`role_id` bigint(32) NOT NULL AUTO_INCREMENT,
`name` varchar(32) NOT NULL,
PRIMARY KEY (`role_id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
/*Data for the table `role` */
insert into `role`(`role_id`,`name`) values (1,'common_user'),(2,'admin');
/*Table structure for table `role_resource` */
DROP TABLE IF EXISTS `role_resource`;
CREATE TABLE `role_resource` (
`role_resource_id` bigint(32) NOT NULL AUTO_INCREMENT,
`role_id` bigint(32) DEFAULT NULL,
`resource_id` bigint(32) DEFAULT NULL,
PRIMARY KEY (`role_resource_id`),
KEY `role_resource_fk_roleId` (`role_id`),
KEY `role_resource_fk_resourceId` (`resource_id`),
CONSTRAINT `role_resource_fk_roleId` FOREIGN KEY (`role_id`) REFERENCES `role` (`role_id`),
CONSTRAINT `role_resource_fk_resourceId` FOREIGN KEY (`resource_id`) REFERENCES `resource` (`resource_id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
/*Data for the table `role_resource` */
insert into `role_resource`(`role_resource_id`,`role_id`,`resource_id`) values (1,1,1),(2,2,2);
/*Table structure for table `user` */
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`user_id` bigint(32) NOT NULL AUTO_INCREMENT,
`username` varchar(32) NOT NULL,
`password` varchar(32) NOT NULL,
`enable` tinyint(1) DEFAULT '0',
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
/*Data for the table `user` */
insert into `user`(`user_id`,`username`,`password`,`enable`) values (1,'li','e10adc3949ba59abbe56e057f20f883e',1),(2,'admin','e10adc3949ba59abbe56e057f20f883e',1);
/*Table structure for table `user_role` */
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
`user_role_id` bigint(32) NOT NULL AUTO_INCREMENT,
`user_id` bigint(32) DEFAULT NULL,
`role_id` bigint(32) DEFAULT NULL,
PRIMARY KEY (`user_role_id`),
KEY `user_role_fk_userId` (`user_id`),
KEY `user_role_fk_roleId` (`role_id`),
CONSTRAINT `user_role_fk_userId` FOREIGN KEY (`user_id`) REFERENCES `user` (`user_id`),
CONSTRAINT `user_role_fk_roleId` FOREIGN KEY (`role_id`) REFERENCES `role` (`role_id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
/*Data for the table `user_role` */
insert into `user_role`(`user_role_id`,`user_id`,`role_id`) values (1,1,1),(2,2,2),(3,2,1);
2、Spring Security权限管理流程图
3、系统程序结构
4、核心类
4.1 SecurityMetadataSourceService
package com.service.impl;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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 com.bean.RoleResource;
import com.service.UserService;
/**
* Spring security 处理用户权限列表查询组合实现类
* 资源源数据定义,将所有的资源和权限对应关系建立起来,即定义某一资源可以被哪些角色去访问,形成权限列表
* @author brushli
* @date 2014-09-02
*/
public class SecurityMetadataSourceService implements FilterInvocationSecurityMetadataSource {
private UserService userService;
public void setUserService(UserService userService) {
this.userService = userService;
}
private static Map> resourceMap = null;
/**
* 加载所有角色与权限的关系
*/
private void loadAllResources() {
if(resourceMap == null) {
resourceMap = new HashMap>();
//得所有角色并且得到这些角色各自拥有什么功能权限
List roleResourceList = null;
try {
roleResourceList = userService.getAllRoleResources();
} catch (Exception e) {
e.printStackTrace();
}
if(roleResourceList != null && !roleResourceList.isEmpty()){
for (RoleResource roleResource : roleResourceList) {
//先检查该角色是否已加入过权限列表,如果是,只需要读取出来,然后继续添加功能权限;如果否,新增该角色的权限列表
Collection configAttributes = resourceMap.get(roleResource.getUrl());
if(configAttributes == null){
configAttributes = new ArrayList();
}
ConfigAttribute configAttribute = new SecurityConfig(roleResource.getRoleName());
configAttributes.add(configAttribute);
resourceMap.put(roleResource.getUrl(), configAttributes);
}
}
}
}
/**
* 返回所请求资源所需要的权限,根据roleName得到模块
*/
public Collection getAttributes(Object object) throws IllegalArgumentException {
String requestUrl = ((FilterInvocation) object).getRequestUrl();
if(resourceMap == null) {
loadAllResources();
}
//判断请求是否有明文参数,如果是,截取参数前的url,不是则直接利用请求url
if(requestUrl.indexOf("?") != -1){
requestUrl = requestUrl.substring(0, requestUrl.indexOf("?"));
}
return resourceMap.get(requestUrl);
}
public Collection getAllConfigAttributes() {
return null;
}
public boolean supports(Class> clazz) {
return true;
}
}
4.2 AccessDecisionManagerService
package com.service.impl;
import java.util.Collection;
import java.util.Iterator;
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;
/**
* spring security决策管理器
* @author brushli
* @date 2014-09-02
*/
public class AccessDecisionManagerService implements AccessDecisionManager{
//In this method, need to compare authentication with configAttributes.
// 1, A object is a URL, a filter was find permission configuration by this URL, and pass to here.
// 2, Check authentication has attribute in permission configuration (configAttributes)
// 3, If not match corresponding authentication, throw a AccessDeniedException.
public void decide(Authentication authentication, Object object,
Collection configAttributes)
throws AccessDeniedException, InsufficientAuthenticationException {
if(configAttributes == null) {
return;
}
//所请求的资源拥有的权限(一个资源对多个权限)
Iterator iterator = configAttributes.iterator();
while(iterator.hasNext()) {
ConfigAttribute configAttribute = iterator.next();
//访问所请求资源所需要的权限
String functionId = configAttribute.getAttribute();
//用户所拥有的权限authentication
for(GrantedAuthority ga : authentication.getAuthorities()) {
if(functionId.equals(ga.getAuthority())) {
return; //放行
}
}
}
//没有权限
throw new AccessDeniedException(" 没有权限访问! ");
}
@Override
public boolean supports(ConfigAttribute attribute) {
return true;
}
@Override
public boolean supports(Class> clazz) {
return true;
}
}
4.3 WebUserDetailsService
package com.service.impl;
import java.util.HashSet;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 com.bean.Role;
import com.bean.User;
import com.bean.UserLoginDetails;
import com.service.UserService;
/**
* 用户登录是准备阶段的业务逻辑
* 用户登陆时会用username去用户表中查出用户的资料,包括密码,再比较输入的密码和用户表中的密码
* @author brushli
* @date 2014-09-02
*/
public class WebUserDetailsService implements UserDetailsService {
protected static final Logger logger = LoggerFactory.getLogger(WebUserDetailsService.class);
private UserService userService;
public void setUserService(UserService userService) {
this.userService = userService;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
boolean accountNonExpired = true;
boolean credentialsNonExpired = true;
boolean accountNonLocked = true;
boolean enabled = true;
User user = null;
try {
user = userService.getUserByUsername(username);
} catch (Exception e) {
e.printStackTrace();
}
if(user == null){
throw new UsernameNotFoundException("User account : " + username + " not found!");
}
//封装该用户具有什么角色
Set authorities = new HashSet();
if(user.getRoles() != null && !user.getRoles().isEmpty()){
for(Role role : user.getRoles()){
GrantedAuthority ga = new SimpleGrantedAuthority(role.getName());
authorities.add(ga);
}
}
UserLoginDetails userLoginDetails = new UserLoginDetails(user.getUsername(), user.getPassword(), authorities, accountNonExpired, accountNonLocked, credentialsNonExpired, enabled);
return userLoginDetails;
}
}
5、配置文件
5.1 web.xml
security
index.jsp
contextConfigLocation
classpath*:applicationContext-datasource.xml,classpath*:applicationContext-security.xml
encoding-filter
org.springframework.web.filter.CharacterEncodingFilter
encoding
UTF-8
encoding-filter
/*
springSecurityFilterChain
org.springframework.web.filter.DelegatingFilterProxy
springSecurityFilterChain
/*
org.springframework.web.context.ContextLoaderListener
org.springframework.security.web.session.HttpSessionEventPublisher
springServlet
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
classpath*:applicationContext-servlet.xml
1
springServlet
*.do
60
5.2 applicationContext-security.xml
5.3 applicationContext-datasource.xml
6、系统的运行效果
待续...
7、系统的下载地址
http://download.csdn.net/detail/brushli/7865697