花了一些时间了解了一下springsecurity
SS配置主要包括如下:
*web.xml和spring中进行相应配置
*UserDetails
*创建过滤器
*用户权限获取
*资源权限获取
*决策类(依据上面两集合进行判断)
applicationContext-security.xml其中Bean的配置主要是应用注释,ss的spring配置都此文件中。
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:s="http://www.springframework.org/schema/security" 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-3.0.xsd" default-lazy-init="true"> <!-- 国际化资源引入 --> <bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource"> <property name="basename" value="classpath:i18nResources_zh_CN.properties" /> </bean> <!-- 设置登录页面为login.jsp且不拦截页面login.jsp后面的*防止带了参数 --> <s:http auto-config="true" access-denied-page="/denied.jsp"> <s:form-login login-page="/login.jsp" default-target-url="/index.jsp" authentication-failure-url="/login.jsp"/> <s:intercept-url pattern="/login.jsp*" filters="none" /> <!-- session失效 --> <s:session-management invalid-session-url="/invalid.jsp" /> <!-- 自定义过滤器 --> <s:custom-filter ref="customFilter" before="FILTER_SECURITY_INTERCEPTOR"/> </s:http> <bean id="customFilter" class="com.sc.ss.security.CustomFilter"> <property name="authenticationManager" ref="authenticationManager"/> <property name="accessDecisionManager" ref="accessDecisionManager"/> <property name="securityMetadataSource" ref="securityMetadataSource"/> </bean> <!-- 认证md5加密密码(原密码加盐值进行加密) --> <s:authentication-manager alias="authenticationManager"> <s:authentication-provider user-service-ref="userDetailManager"> <s:password-encoder hash="md5"> <s:salt-source user-property="username"/> </s:password-encoder> </s:authentication-provider> </s:authentication-manager> </beans>
applicationContext.xml配置:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <!-- 当使用<context:component-scan base-package="com.cc" />时, 就无需使用<context:annotation-config />, 因为<context:component-scan base-package="com.cc" /> 会开始<context:annotation-config />里面包含的处理器 --> <context:annotation-config /> <!-- 自动查找配置的bean --> <context:component-scan base-package="com.sc.ss" /> <!-- **********引入hibernate的配置属性文件************ --> <bean id="extendedPropertyPlaceholderConfigurer" class="com.scommon.util.properties.ExtendedPropertyPlaceholderConfigurer"> <property name="locations"> <list> <value>classpath:hibernate.properties</value> <value>classpath:global.properties</value> </list> </property> </bean> <!-- ****************************************数据源*************************************************************** --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"> <!-- 指定连接数据库的驱动 --> <property name="driverClass" value="${driver_class}" /> <!-- 指定连接数据库的URL --> <!-- &zeroDateTimeBehavior=convertToNull是为了避免因数据库中为date类型的数据为0000-00-00, 若为此值hibernate会报错‘Cannot convert value '0000-00-00 00:00:00' from column 1 to TIMESTAMP’ --> <property name="jdbcUrl" value="${url}" /> <!-- 指定连接数据库的用户名 --> <property name="user" value="${username}" /> <!-- 指定连接数据库的密码 --> <property name="password" value="${password}" /> <!-- 指定连接数据库连接池的最大连接数 --> <property name="maxPoolSize" value="${maxPoolSize}" /> <!-- 指定连接数据库连接池的最小连接数 --> <property name="minPoolSize" value="${minPoolSize}" /> <!-- 指定连接数据库连接池的初始化连接数 --> <property name="initialPoolSize" value="${initialPoolSize}" /> <!-- 指定连接数据库连接池的连接的最大空闲时间 --> <property name="maxIdleTime" value="${maxIdleTime}" /> <property name="idleConnectionTestPeriod" value="${idleConnectionTestPeriod}" /> </bean> <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"> <property name="dataSource"> <ref bean="dataSource" /> </property> <!-- 自动搜索包里面的实体类 --> <property name="packagesToScan"> <list> <value>com.sc.ss.model</value> </list> </property> <property name="hibernateProperties"> <props> <prop key="hibernate.dialect">${dialect}</prop> <prop key="hibernate.show_sql">${show_sql}</prop> <prop key="hibernate.format_sql">${format_sql}</prop> <prop key="hibernate.hbm2ddl.auto">${auto}</prop> </props> </property> </bean> <!-- 配置事务 begin --> <bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean> <bean id="txJDBCManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <tx:method name="*" propagation="REQUIRED" /> </tx:attributes> </tx:advice> <tx:advice id="txJDBCAdvice" transaction-manager="txJDBCManager"> <tx:attributes> <tx:method name="*" propagation="REQUIRED" /> </tx:attributes> </tx:advice> <aop:config> <aop:pointcut id="bussinessService" expression="execution(public * com.sc.ss.service.*.*(..))" /> <aop:advisor pointcut-ref="bussinessService" advice-ref="txAdvice" /> </aop:config> <aop:config> <aop:pointcut expression="execution(public * com.sc.ss.security.*.*(..))" id="jdbcBussinessService" /> <aop:advisor pointcut-ref="jdbcBussinessService" advice-ref="txJDBCAdvice" /> </aop:config> <!-- 配置事务 end --> </beans>
web.xml文件:
<!-- ss3过滤器链 --> <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>
sql文件:
DROP TABLE IF EXISTS `authorities`;
CREATE TABLE `authorities` (
`id` varchar(32) COLLATE utf8_bin NOT NULL,
`name` varchar(20) COLLATE utf8_bin NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
/*Data for the table `authorities` */
insert into `authorities`(`id`,`name`) values ('1312869365835e2baaac768e4c079c14','发贴'),('13128693bb8a5e2baaac768e4c057d23','进入系统'),('13128693bb8a5e2baaac768e4c079c14','删帖');
/*Table structure for table `authorities_resources` */
DROP TABLE IF EXISTS `authorities_resources`;
CREATE TABLE `authorities_resources` (
`id` varchar(32) COLLATE utf8_bin NOT NULL,
`authorities` varchar(32) COLLATE utf8_bin DEFAULT NULL,
`resource` varchar(32) COLLATE utf8_bin DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `FK401C1687DEFE40` (`authorities`),
KEY `FK401C168771EB41B1` (`resource`),
CONSTRAINT `FK401C168771EB41B1` FOREIGN KEY (`resource`) REFERENCES `resources` (`id`),
CONSTRAINT `FK401C1687DEFE40` FOREIGN KEY (`authorities`) REFERENCES `authorities` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
/*Data for the table `authorities_resources` */
insert into `authorities_resources`(`id`,`authorities`,`resource`) values ('1312869365835e2baabbaaeeec079c14','1312869365835e2baaac768e4c079c14','1312869365835e2baaacaaae4c079c14'),('13128693bb8b3e2bacdc768e4c057d23','13128693bb8a5e2baaac768e4c057d23','13128abcbb8bbe55abac768e4c079c14'),('13128abcbb8aaa2daaac768e4c079c14','13128693bb8a5e2baaac768e4c079c14','13128abcbb8a5e23aaac768e4c079c14');
/*Table structure for table `resources` */
DROP TABLE IF EXISTS `resources`;
CREATE TABLE `resources` (
`id` varchar(32) COLLATE utf8_bin NOT NULL,
`describle` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`enabled` int(11) DEFAULT NULL,
`name` varchar(32) COLLATE utf8_bin NOT NULL,
`url` varchar(20) COLLATE utf8_bin NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
/*Data for the table `resources` */
insert into `resources`(`id`,`describle`,`enabled`,`name`,`url`) values ('1312869365835e2baaacaaae4c079c14',NULL,1,'发贴','/post.action'),('13128abcbb8a5e23aaac768e4c079c14',NULL,1,'删帖','/delete.action'),('13128abcbb8bbe55abac768e4c079c14',NULL,1,'进入系统','/index.jsp');
/*Table structure for table `role_authorities` */
DROP TABLE IF EXISTS `role_authorities`;
CREATE TABLE `role_authorities` (
`id` varchar(32) COLLATE utf8_bin NOT NULL,
`authorities` varchar(32) COLLATE utf8_bin DEFAULT NULL,
`role` varchar(32) COLLATE utf8_bin DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `FKD255F0D8DEFE40` (`authorities`),
KEY `FKD255F0D8A8FA3DB1` (`role`),
CONSTRAINT `FKD255F0D8A8FA3DB1` FOREIGN KEY (`role`) REFERENCES `roles` (`id`),
CONSTRAINT `FKD255F0D8DEFE40` FOREIGN KEY (`authorities`) REFERENCES `authorities` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
/*Data for the table `role_authorities` */
insert into `role_authorities`(`id`,`authorities`,`role`) values ('13128633253bbaab9c3c768e4c079c14','1312869365835e2baaac768e4c079c14','1312863325335e1b9c3c768e4c079c14'),('1312869363835e2babcd768e4c079c14','13128693bb8a5e2baaac768e4c079c14','1312863325335e1b9c3c768e4c079c14'),('1312869365835e2b4cab76834c079c45','1312869365835e2baaac768e4c079c14','1312869365835e2b4c3c768e4c079c14'),('13128693bb8a5e2baaac768ejcab7d23','13128693bb8a5e2baaac768e4c057d23','1312869365835e2b4c3c768e4c079c14'),('13128693ew8a5e2maaac768e4c057d23','13128693bb8a5e2baaac768e4c057d23','1312863325335e1b9c3c768e4c079c14');
/*Table structure for table `roles` */
DROP TABLE IF EXISTS `roles`;
CREATE TABLE `roles` (
`id` varchar(32) COLLATE utf8_bin NOT NULL,
`name` varchar(30) COLLATE utf8_bin NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
/*Data for the table `roles` */
insert into `roles`(`id`,`name`) values ('1312863325335e1b9c3c768e4c079c14','管理员'),('1312869365835e2b4c3c768e4c079c14','普通用户');
/*Table structure for table `user_roles` */
DROP TABLE IF EXISTS `user_roles`;
CREATE TABLE `user_roles` (
`id` varchar(32) COLLATE utf8_bin NOT NULL,
`role` varchar(32) COLLATE utf8_bin DEFAULT NULL,
`user` varchar(32) COLLATE utf8_bin DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `FK73429949A8FA3DB1` (`role`),
KEY `FK73429949A927A851` (`user`),
CONSTRAINT `FK73429949A927A851` FOREIGN KEY (`user`) REFERENCES `users` (`id`),
CONSTRAINT `FK73429949A8FA3DB1` FOREIGN KEY (`role`) REFERENCES `roles` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
/*Data for the table `user_roles` */
insert into `user_roles`(`id`,`role`,`user`) values ('1312863323335a1b9b3c768s4c079c14','1312869365835e2b4c3c768e4c079c14','1312869365235e1b9c4c768e4c079c15'),('1312869365835e2b4c3c263e4a079c22','1312863325335e1b9c3c768e4c079c14','1312869365235e1b9c3c768e4c079c14');
/*Table structure for table `users` */
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
`id` varchar(32) COLLATE utf8_bin NOT NULL,
`enabled` int(11) NOT NULL,
`password` varchar(50) COLLATE utf8_bin NOT NULL,
`username` varchar(20) COLLATE utf8_bin NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
/*Data for the table `users` */
insert into `users`(`id`,`enabled`,`password`,`username`) values ('1312869365235e1b9c3c768e4c079c14',1,'ceb4f32325eda6142bd65215f4c0f371','admin'),('1312869365235e1b9c4c768e4c079c15',1,'47a733d60998c719cf3526ae7d106d13','user');
/* ClassName:AccessDecisionManager
* Function: 决策类
* 当请求访问时,判断用户是否具有访问所需的所有权限
* @author sux
* @version 1.0, 2011-8-17
* @since ss31.0
*/
@Component("accessDecisionManager")
public class AccessDecisionManager implements org.springframework.security.access.AccessDecisionManager{
org.apache.commons.logging.Log log = LogFactory.getLog(AccessDecisionManager.class);
/**
* authentication用户认证后 存有用户的所有权限
* configAttributes访问所需要的权限
* 若无权则抛出异常
*/
public void decide(Authentication authentication, Object arg1, Collection<ConfigAttribute> configAttributes)
throws AccessDeniedException, InsufficientAuthenticationException {
if(null == configAttributes){
return;
}
for(ConfigAttribute configAttribute : configAttributes){
for(GrantedAuthority gAuthority : authentication.getAuthorities()){
log.info("conf="+configAttribute.getAttribute()+" auth="+gAuthority.getAuthority());
if(configAttribute.getAttribute().trim().equals(gAuthority.getAuthority().trim())){
return;
}
}
}
//无权限抛出拒绝异常
throw new AccessDeniedException("");
}
public boolean supports(ConfigAttribute arg0) {
return true;
}
public boolean supports(Class<?> arg0) {
return true;
}
}
/**
* ClassName:CustomFilter
* Function: TODO ADD FUNCTION
*
* @author sux
* @version 1.0, 2011-8-16
* @since ss31.0
*/
public class CustomFilter extends AbstractSecurityInterceptor implements Filter{
private FilterInvocationSecurityMetadataSource securityMetadataSource;
@Override
public Class<? extends Object> getSecureObjectClass() {
return FilterInvocation.class;
}
@Override
public SecurityMetadataSource obtainSecurityMetadataSource() {
return this.securityMetadataSource;
}
public void destroy() {
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain);
InterceptorStatusToken token = super.beforeInvocation(fi);
try{
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}finally{
super.afterInvocation(token, null);
}
}
public void init(FilterConfig arg0) throws ServletException {
}
public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {
return securityMetadataSource;
}
public void setSecurityMetadataSource(FilterInvocationSecurityMetadataSource securityMetadataSource) {
this.securityMetadataSource = securityMetadataSource;
}
}
/**
* ClassName:SecurityMetadataSource
* Function: 资源与权限建立管理
* 在服务器启动时就加载所有访问URL所需的权限,存入resourceMap集合中。
* Spring在设置完一个bean所有的合作者后,会检查bean是否实现了InitializingBean接口,
* 如果实现就调用bean的afterPropertiesSet方法。 但这样便造成bean和spring的耦合,
* 最好用init-method
* @author sux
* @version 1.0, 2011-8-17
* @since ss31.0
*/
@Component("securityMetadataSource")
public class SecurityMetadataSource implements FilterInvocationSecurityMetadataSource, InitializingBean{
org.apache.commons.logging.Log log = LogFactory.getLog(SecurityMetadataSource.class);
private UrlMatcher urlMatcher = new AntUrlPathMatcher();
private Map<String, Collection<ConfigAttribute>> resourceMap = null;
private AuthoritiesDao authoritiesDao;
private ResourcesDao resourcesDao;
/**
* 构造方法中建立请求url(key)与权限(value)的关系集合
*/
public void afterPropertiesSet() throws Exception {
//查询出所有资源
List<String> urls = resourcesDao.findAllUrl();
Collection<ConfigAttribute> cAttributes = null;
resourceMap = new HashMap<String, Collection<ConfigAttribute>>();
for(String url : urls){
List<String> auths = authoritiesDao.findNameByUrl(url);
if(resourceMap.containsKey(url)){
cAttributes = resourceMap.get(url);
}else{
cAttributes = new ArrayList<ConfigAttribute>();
}
for(String auth : auths){
ConfigAttribute ca = new SecurityConfig(auth);
cAttributes.add(ca);
}
resourceMap.put(url, cAttributes);
}
log.info(resourceMap);
}
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
/**
* 根据请求的url从集合中查询出所需权限
*/
public Collection<ConfigAttribute> getAttributes(Object arg0) throws IllegalArgumentException {
String url = ((FilterInvocation) arg0).getRequestUrl();
log.info("request url is "+url);
int position = url.indexOf("?");
if(-1 != position){
url = url.substring(0, position);
}
Iterator<String> it = resourceMap.keySet().iterator();
while(it.hasNext()){
//判断两地址是否相同
if(urlMatcher.pathMatchesUrl(url, it.next())){
log.info(resourceMap.get(url));
return resourceMap.get(url);
}
}
return null;
}
/**
* 返回false则报错
* SecurityMetadataSource does not support secure object class:
* class org.springframework.security.web.FilterInvocation
*/
public boolean supports(Class<?> arg0) {
return true;
}
public AuthoritiesDao getAuthoritiesDao() {
return authoritiesDao;
}
@Resource
public void setAuthoritiesDao(AuthoritiesDao authoritiesDao) {
this.authoritiesDao = authoritiesDao;
}
public ResourcesDao getResourcesDao() {
return resourcesDao;
}
@Resource
public void setResourcesDao(ResourcesDao resourcesDao) {
this.resourcesDao = resourcesDao;
}
}
/**
* ClassName:UserDetailManager
* Function: 查询出用户所具有的权限等信息并进行封装得到UserDetails
*
* @author sux
* @version 1.0, 2011-8-16
* @since ss31.0
*/
@Component("userDetailManager")
public class UserDetailManager implements UserDetailsService {
org.apache.commons.logging.Log log = LogFactory.getLog(UserDetailManager.class);
private UsersDao usersDao;
private AuthoritiesDao authoritiesDao;
/**
* 根据用户名取得及权限等信息
*/
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
Users user = usersDao.findByUsername(username);
List<String> authoritiesNames = authoritiesDao.findNameByUsername(username);
log.info(authoritiesNames);
List<GrantedAuthority> gAuthoritys = new ArrayList<GrantedAuthority>();
for (String authoritiesName : authoritiesNames) {
GrantedAuthorityImpl gai = new GrantedAuthorityImpl(authoritiesName);
gAuthoritys.add(gai);
}
return new CustomUserDetails(user.getPassword(), user.getUsername(), true, true, true, true, gAuthoritys);
}
public UsersDao getUsersDao() {
return usersDao;
}
@Resource
public void setUsersDao(UsersDao usersDao) {
this.usersDao = usersDao;
}
public AuthoritiesDao getAuthoritiesDao() {
return authoritiesDao;
}
@Resource
public void setAuthoritiesDao(AuthoritiesDao authoritiesDao) {
this.authoritiesDao = authoritiesDao;
}
}
/**
* ClassName:UserDetails
* Function:创建UserDetails的实现类,本质为用户详细信息的一个载体
* 在实现类中可以进一步扩展属性
*
* @author sux
* @version 1.0, 2011-8-16
* @since ss31.0
*/
public class CustomUserDetails implements UserDetails {
private Collection<GrantedAuthority> authorities;
private String password;
private String username;
private boolean accountNonExpired;
private boolean accountNonLocked;
private boolean credentialsNonExpired;
private boolean enabled;
public CustomUserDetails(String password, String username, boolean accountNonExpired, boolean accountNonLocked,
boolean credentialsNonExpired, boolean enabled, List<GrantedAuthority> gAuthoritys) {
this.password = password;
this.username = username;
this.accountNonExpired = accountNonExpired;
this.accountNonLocked = accountNonLocked;
this.credentialsNonExpired = credentialsNonExpired;
this.enabled = enabled;
this.authorities = gAuthoritys;
}
public Collection<GrantedAuthority> getAuthorities() {
return this.authorities;
}
public String getPassword() {
return this.password;
}
public String getUsername() {
return this.username;
}
public boolean isAccountNonExpired() {
return this.accountNonExpired;
}
public boolean isAccountNonLocked() {
return this.accountNonLocked;
}
public boolean isCredentialsNonExpired() {
return this.credentialsNonExpired;
}
public boolean isEnabled() {
return this.enabled;
}
}
DaoClass:
@Repository("authoritiesDao")
public class AuthoritiesDaoImpl extends BaseDao<Authorities> implements AuthoritiesDao {
public List<String> findNameByUsername(String username) {
String sql = "SELECT a.name FROM users u, user_roles ur, roles r, role_authorities ra, authorities a WHERE u.username = ? AND u.id = ur.user AND r.id = ur.role AND r.id = ra.role AND a.id = ra.authorities";
List<String> authoritiesNames = super.findBySQL(sql, username);
return authoritiesNames;
}
public List<String> findNameByUrl(String url) {
String sql = "select a.name from authorities a, authorities_resources ar, resources r "
+ "where r.url = ? and r.id = ar.resource and a.id = ar.authorities";
return super.findBySQL(sql, url);
}
}
login.jsp:
<html>
<head>
<base href="<%=basePath%>">
<title>login</title>
</head>
其中name和action都是固定的
<body>
${sessionScope.SPRING_SECURITY_LAST_EXCEPTION.message }
<form method="post" action="j_spring_security_check">
username: <input type="text" name="j_username"/><br/>
password: <input type="password" name="j_password"/><br/>
<input type="submit" value="submit"/>
</form>
</body>
</html>
index.jsp:
<body>
index.jsp
<a href="post.action">post</a>
<a href="delete.action">delete</a>
</body>
编辑器中不知道为什么不能输入中文了?
从上面的SQL脚本可知post.action和delete.action作为资源保存在资源表中,user具有post权限但没有,delete权限,而admin两者都有。在点击超链接时请求的地址就会进入创建的过滤器中,通过决策类判断用户是否有权限进行访问,无权限是跳转到denied.jsp页面。