在现在的应用当中,我们都会把用户信息都存到数据库当中,所以我们要把security基于内存的操作改为基于数据库的操作。
其实不管基于内存的操作还是基于数据库的操作,security的目的都是只有一个,拿到当前的User以及相关的UserDetails信息,在基于内存的时候,用户名以及相关的UserDetails都会存到内存中,同理,如果迁移到数据库中,那么,我们可以从数据库中查出当前User以及相关的用户信息,然后封装成一个实现了UserDetails接口的User实现类。理解到这里,目标已经非常明确了:返回一个实现了UserDetails的User类给框架。
在security框架中,无论是InMemoryDaoImpl还是基于数据库的实现类都是利用同一个接口UserDetailService的方法loadUserByUsername。(这种面向接口的设计方式是非常棒的,纯属个人的感悟)。这里我们只需要改变这个方法的实现方式就可以达到目的了。
到这里,我们已经知道怎么样去把基于内存的操作迁移到数据库中了,下面的实现,只要按照这种思路来做,就不会有什么问题了。
首先,我们需要创建一个类实现UserDetailService接口,并且实现方法loadUserByUsername(下面是笔者自己的例子,仅供参考):
package service;
import java.util.HashSet;
import java.util.Set;
import javax.annotation.Resource;
import model.Users;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import dao.BaseCollectionDao;
@Service("hibernateUserDetailsService")
public class HibernateUserDetailsService implements UserDetailsService {
private BaseCollectionDao baseCollectionDao;
@Resource(name="baseCollectionDao")
public void setBaseCollectionDao(BaseCollectionDao baseCollectionDao)
{
this.baseCollectionDao = baseCollectionDao;
}
@Transactional
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException
{
Users users = baseCollectionDao.findUniqueByProperty(Users.class, "username", username);
if(users == null)
throw new UsernameNotFoundException("用户" + username + " 不存在!!!");
String[] roles = users.getRoles().split(",");
Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
for(String role : roles)
{
authorities.add(new SimpleGrantedAuthority(role));
}
return new User(users.getUsername(), users.getPassword(), authorities);
}
}
从上面的代码可以看到,User是从数据库查找出来的,而基于内存的操作,用户及其用户信息都是放到Map中的。为了方便对比,把InMemoryDaoImpl的实现方法也贴出来:
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return userMap.getUser(username);
}
从上面两段代码的对比就可以看出,基于内存操作和基于数据库操作的根本区别。
接下来,定义好了UserDetailService,那么就应用到security框架中,在之前的篇章中已经说过了怎么配置,这里就简单贴出代码就行了:
<authentication-manager alias="authenticationManager">
<authentication-provider user-service-ref="hibernateUserDetailsService">
</authentication-provider>
</authentication-manager>
因为dao层的操作每个人都不同,而这个问题并不重要,最主要的是把UserDetailService的loadUserByUsername的方法用基于数据库查询的方式实现。所以这里就不关注dao层的实现了。其实这里的变化就是把InMemoryDaoImpl类,换成了HibernateUserDetailService。
下面贴出本人的从service层到dao层的实现(security登录不需要controller层),以及配置文件(仅供参考):
hibernateUserDetailService:
package service;
import java.util.HashSet;
import java.util.Set;
import javax.annotation.Resource;
import model.Users;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import dao.BaseCollectionDao;
@Service("hibernateUserDetailsService")
public class HibernateUserDetailsService implements UserDetailsService {
private BaseCollectionDao baseCollectionDao;
@Resource(name="baseCollectionDao")
public void setBaseCollectionDao(BaseCollectionDao baseCollectionDao)
{
this.baseCollectionDao = baseCollectionDao;
}
@Transactional
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException
{
Users users = baseCollectionDao.findUniqueByProperty(Users.class, "username", username);
if(users == null)
throw new UsernameNotFoundException("用户" + username + " 不存在!!!");
String[] roles = users.getRoles().split(",");
Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
for(String role : roles)
{
authorities.add(new SimpleGrantedAuthority(role));
}
return new User(users.getUsername(), users.getPassword(), authorities);
}
}
dao层:
public <T> T findUniqueByProperty(Class<T> entityClass,final String propertyName,final Object value)
{
Session session = sessionFactory.getCurrentSession();
Criteria criteria = session.createCriteria(entityClass)
.add( Restrictions.eq(propertyName, value) ); //增加属性相等约束
return (T) criteria.uniqueResult();
}
model层:
package model;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import ;
import javax.persistence.Table;
@Entity
@Table(name="users")
public class Users {
private int id;
private String username;
private String password;
private String roles;
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getRoles() {
return roles;
}
public void setRoles(String roles) {
this.roles = roles;
}
}
security配置文件:
<?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.1.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.1.xsd">
<!-- 自上而下读取配置文件 -->
<!-- 登录页面不过滤 -->
<http pattern="/login.jsp*" security="none"/>
<http pattern="/logoutsuccess.jsp" security="none" />
<http auto-config="true" use-expressions="true" >
<form-login login-page="/login.jsp" always-use-default-target="true" default-target-url="/home.jsp"/>
<intercept-url pattern="/**" access="hasRole('ROLE_USER')"/>
<logout invalidate-session="true" logout-success-url="/logoutsuccess.jsp" logout-url="/logout"/>
</http>
<authentication-manager alias="authenticationManager">
<authentication-provider user-service-ref="hibernateUserDetailsService">
</authentication-provider>
</authentication-manager>
</beans:beans>
数据库配置文件:
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd">
<!-- c3p0 dataSource -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!-- 加载驱动 -->
<property name="driverClass" value="com.mysql.jdbc.Driver" />
<!-- database url -->
<property name="jdbcUrl" value="jdbc:mysql://localhost/security" />
<!-- database username -->
<property name="user" value="root" />
<!-- database password -->
<property name="password" value="root" />
<!-- 连接关闭时默认将所有未提交的操作回滚。默认为false -->
<property name="autoCommitOnClose" value="false" />
<!-- 连接池中保留的最小连接数 -->
<property name="minPoolSize" value="5" />
<!-- 连接池中保留的最大连接数。默认为15 -->
<property name="maxPoolSize" value="15" />
<!-- 初始化时获取的连接数,取值应在minPoolSize与maxPoolSize之间。默认为3 -->
<property name="initialPoolSize" value="5" />
<!-- 最大空闲时间,超过空闲时间的连接将被丢弃。为0或负数则永不丢弃。默认为0秒 -->
<property name="maxIdleTime" value="0" />
<!-- 当连接池中的连接用完时,C3P0一次性创建新连接的数目。默认为3 -->
<property name="acquireIncrement" value="3" />
<!-- 定义在从数据库获取新连接失败后重复尝试获取的次数,默认为30 -->
<property name="acquireRetryAttempts" value="30" />
<!-- 当连接池用完时客户端调用getConnection()后等待获取新连接的时间,超时后将抛出SQLException,如设为0则无限期等待。单位毫秒,默认为0 -->
<property name="checkoutTimeout" value="0" />
</bean>
<!-- sessionFactory -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="hibernateProperties">
<value>
hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
<!-- cureentSesssion -->
hibernate.current_session_context_class=org.springframework.orm.hibernate4.SpringSessionContext
<!-- Second-level caching
hibernate.cache.region.factory_class=org.hibernate.cache.EhCacheRegionFactory
net.sf.ehcache.configurationResourceName=/ehcache.xml
hibernate.cache.use_second_level_cache=true
hibernate.cache.use_query_cache=true -->
<!-- sql -->
hibernate.show_sql=true
hibernate.format_sql=true
hibernate.hbm2ddl.auto=update
</value>
</property>
<property name="packagesToScan">
<value>model</value>
</property>
</bean>
<!-- 事务管理 -->
<bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
<!-- dao -->
<context:component-scan base-package="dao.impl"></context:component-scan>
</beans>
springMvc的配置:
<?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:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd">
<mvc:annotation-driven/>
<context:component-scan base-package="controller"/>
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
</bean>
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
</bean>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>
上面就是本人的实现代码,由于使用的框架的不同,所以在配置方面会有所不同,仅供参考。
















