spring security 阶段总结: mvc + hibernate +mysql 实现的例子

利用业余时间初步研究了几天 spring security, 现在是时候做一个阶段性总结了,利用 spring mvc , spring security, hibernate, mysql 实现一个基于数据库的简单权限系统, 功能不复杂,但麻雀虽小,五脏俱全。目标如下:
1. 用户和角色存储在数据库中
2. 不同的角色访问不同的页面. 如果不没有权限,则出 403 错误页面 (可以参考前面的文章定制一个.), ROLE_USER  角色的用户,只能访问user 页面,而 ROLE_ADMIN 角色的用户可以访问 admin1,admin2 user页面.

提供整个程序的代码下载。在本文的最后,应该本文只挑重点的讲,其他的可以看代码.
准备, 我用到的jar 包如下,有可能多余, 但注意 hibernate 需要的包包含jboss的包是不可以缺少的,清单如下:
程序代码 程序代码

antlr-2.7.7.jar
aopalliance.jar
aspectjweaver.jar
commons-collections.jar
commons-logging-1.1.jar
dom4j-1.6.1.jar
ehcache-core-2.4.3.jar
hibernate-commons-annotations-4.0.1.Final.jar
hibernate-core-4.0.0.Final.jar
hibernate-ehcache-4.0.0.Final.jar
hibernate-entitymanager-4.0.0.Final.jar
hibernate-jpa-2.0-api-1.0.1.Final.jar
javassist-3.12.1.GA.jar
jboss-logging-3.1.0.CR2.jar
jboss-transaction-api_1.1_spec-1.0.0.Final.jar
jstl-1.1.2.jar
log4j-1.2.13.jar
mysql-connector-java-3.1.12-bin.jar
slf4j-api-1.5.2.jar
slf4j-log4j12-1.5.2.jar
spring-aop-3.2.4.RELEASE.jar
spring-aspects-3.2.4.RELEASE.jar
spring-beans-3.2.4.RELEASE.jar
spring-context-3.2.4.RELEASE.jar
spring-context-support-3.2.4.RELEASE.jar
spring-core-3.2.4.RELEASE.jar
spring-e­xpression-3.2.4.RELEASE.jar
spring-jdbc-3.2.4.RELEASE.jar
spring-orm-3.2.4.RELEASE.jar
spring-security-config-3.1.4.RELEASE.jar
spring-security-core-3.1.4.RELEASE.jar
spring-security-web-3.1.4.RELEASE.jar
spring-tx-3.2.4.RELEASE.jar
spring-web-3.2.4.RELEASE.jar
spring-webmvc-3.2.4.RELEASE.jar
standard-1.1.2.jar


数据库的准备
程序代码 程序代码


-- ----------------------------
-- Table structure for `roles`
-- ----------------------------
Drop TABLE IF EXISTS `roles`;
Create TABLE `roles` (
  `id` int(6) NOT NULL AUTO_INCREMENT,
  `role` varchar(20) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of roles
-- ----------------------------
Insert INTO `roles` VALUES ('1', 'ROLE_ADMIN');
Insert INTO `roles` VALUES ('2', 'ROLE_USER');

-- ----------------------------
-- Table structure for `users`
-- ----------------------------
Drop TABLE IF EXISTS `users`;
Create TABLE `users` (
  `id` int(6) NOT NULL AUTO_INCREMENT,
  `login` varchar(20) NOT NULL,
  `password` varchar(20) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of users
-- ----------------------------
Insert INTO `users` VALUES ('1', 'yihaomen', '123456');
Insert INTO `users` VALUES ('2', 'admin', '123456');

-- ----------------------------
-- Table structure for `user_roles`
-- ----------------------------
Drop TABLE IF EXISTS `user_roles`;
Create TABLE `user_roles` (
  `user_id` int(6) NOT NULL,
  `role_id` int(6) NOT NULL,
  KEY `user` (`user_id`),
  KEY `role` (`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of user_roles
-- ----------------------------
Insert INTO `user_roles` VALUES ('1', '2');
Insert INTO `user_roles` VALUES ('2', '1');


整个工程如下图所示:



至于 spring security 的基础配置,不解释了,只想详细介绍下 CustomUserDetailsService , 这是关键,用户认证,角色的获取以及授权都是这样获得的.

程序代码 程序代码

package com.yihaomen.service;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

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.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 com.yihaomen.dao.UserDAO;

@Service
@Transactional(readOnly=true)
public class CustomUserDetailsService implements UserDetailsService {
    
    @Autowired
    private UserDAO userDAO;    

    public UserDetails loadUserByUsername(String login)
            throws UsernameNotFoundException {
        /*这里是认证*/
        com.yihaomen.model.User domainUser = userDAO.getUser(login);
        
        boolean enabled = true;
        boolean accountNonExpired = true;
        boolean credentialsNonExpired = true;
        boolean accountNonLocked = true;

        return new User(
                domainUser.getLogin(),
                domainUser.getPassword(),
                enabled,
                accountNonExpired,
                credentialsNonExpired,
                accountNonLocked,
                getAuthorities(domainUser.getRole().getId())
        );
    }
    
    public Collection<? extends GrantedAuthority> getAuthorities(Integer role) {
        List<GrantedAuthority> authList = getGrantedAuthorities(getRoles(role));
        return authList;
    }
    
    public List<String> getRoles(Integer role) {

        List<String> roles = new ArrayList<String>();
        /*这里还可以写的更灵活或者更好,暂且这样吧. hardcode的,做灵活也容易,试验局,就这样了。*/
        if (role.intValue() == 1) {
            roles.add("ROLE_USER");
            roles.add("ROLE_ADMIN");
        } else if (role.intValue() == 2) {
            roles.add("ROLE_USER");
        }
        return roles;
    }
    
    public static List<GrantedAuthority> getGrantedAuthorities(List<String> roles) {
        /*这是授权*/
        List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();        
        for (String role : roles) {
            authorities.add(new SimpleGrantedAuthority(role));
        }
        return authorities;
    }

}



spring security 配置文件核心部分如下
程序代码 程序代码

<http pattern="/app/user-login" security="none" />
    <http auto-config="true">    
        <intercept-url pattern="/app/sec/moderation" access="ROLE_USER" />
        <intercept-url pattern="/app/admin/*" access="ROLE_ADMIN" />
        
        <form-login login-page="/app/user-login"
        default-target-url="/app/success-login"
        authentication-failure-url="/app/error-login" />
            
        <logout logout-success-url="/app/index" />    
    </http>
    
    <beans:bean id="customUserDetailsService"    class="com.yihaomen.service.CustomUserDetailsService" />
    
    
    <authentication-manager>
        <authentication-provider user-service-ref="customUserDetailsService">
            <password-encoder hash="plaintext">
        </password-encoder></authentication-provider>
    </authentication-manager>


spring 与 hibernate4 的集成

程序代码 程序代码

<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
            <list>
                <value>classpath:resources.properties</value>
            </list>
        </property>
    </bean>

    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="${connection.driver_class}" />
        <property name="url" value="${connection.url}" />
        <property name="username" value="${connection.username}" />
        <property name="password" value="${connection.password}" />
    </bean>
    
    
    
    
    <bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean" >
        <property name="dataSource" ref="dataSource"/>
        
        <property name="packagesToScan">
            <list>
                <value>com.yihaomen</value>
            </list>
        </property>
        
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">${hibernate.dialect}</prop>
                <prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
                <prop key="hibernate.format_sql">true</prop>
                <prop key="hibernate.query.substitutions">${hibernate.query.substitutions}</prop>
                <prop key="hibernate.default_batch_fetch_size">${hibernate.default_batch_fetch_size}</prop>
                <prop key="hibernate.max_fetch_depth">${hibernate.max_fetch_depth}</prop>
                <prop key="hibernate.generate_statistics">${hibernate.generate_statistics}</prop>
                <prop key="hibernate.bytecode.use_reflection_optimizer">${hibernate.bytecode.use_reflection_optimizer}</prop>

                <prop key="hibernate.cache.use_second_level_cache">${hibernate.cache.use_second_level_cache}</prop>
                <prop key="hibernate.cache.use_query_cache">${hibernate.cache.use_query_cache}</prop>
                <prop key="hibernate.cache.region.factory_class">${hibernate.cache.region.factory_class}</prop>
                <prop key="net.sf.ehcache.configurationResourceName">${net.sf.ehcache.configurationResourceName}</prop>
                <prop key="hibernate.cache.use_structured_entries">${hibernate.cache.use_structured_entries}</prop>
            </props>
        </property>
      </bean>


    <!-- 开启AOP监听 只对当前配置文件有效 -->
    <aop:aspectj-autoproxy expose-proxy="true"/>
    
    <!-- 开启注解事务 只对当前配置文件有效 -->
      <tx:annotation-driven transaction-manager="txManager"/>

    <bean id="txManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>

    <tx:advice id="txAdvice" transaction-manager="txManager">
        <tx:attributes>
            <tx:method name="save*" propagation="REQUIRED" />
            <tx:method name="add*" propagation="REQUIRED" />
            <tx:method name="create*" propagation="REQUIRED" />
            <tx:method name="insert*" propagation="REQUIRED" />
            <tx:method name="update*" propagation="REQUIRED" />
            <tx:method name="merge*" propagation="REQUIRED" />
            <tx:method name="del*" propagation="REQUIRED" />
            <tx:method name="remove*" propagation="REQUIRED" />
            <tx:method name="put*" propagation="REQUIRED" />
            <tx:method name="use*" propagation="REQUIRED"/>
            <!--hibernate4必须配置为开启事务 否则 getCurrentSession()获取不到-->
            <tx:method name="get*" propagation="REQUIRED" read-only="true" />
            <tx:method name="count*" propagation="REQUIRED" read-only="true" />
            <tx:method name="find*" propagation="REQUIRED" read-only="true" />
            <tx:method name="list*" propagation="REQUIRED" read-only="true" />
            <tx:method name="*" read-only="true" />
        </tx:attributes>
    </tx:advice>
    <aop:config expose-proxy="true">
        <!-- 只对业务逻辑层实施事务 -->
        <aop:pointcut id="txPointcut" e­xpression="execution(* com.yihaomen.service..*.*(..))" />
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
    </aop:config>
        


用户实体类,角色实体类
程序代码 程序代码

package com.yihaomen.model;

import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.OneToMany;
import javax.persistence.Table;

@Entity
@Table(name="roles")
public class Role {
    
    @Id
    @GeneratedValue
    private Integer id;
    
    private String role;
    
    @OneToMany(cascade=CascadeType.ALL)
    @JoinTable(name="user_roles",
        joinColumns = {@JoinColumn(name="role_id", referencedColumnName="id")},
        inverseJoinColumns = {@JoinColumn(name="user_id", referencedColumnName="id")}
    )
    private Set<User> userRoles;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getRole() {
        return role;
    }

    public void setRole(String role) {
        this.role = role;
    }

    public Set<User> getUserRoles() {
        return userRoles;
    }

    public void setUserRoles(Set<User> userRoles) {
        this.userRoles = userRoles;
    }
    
}

package com.yihaomen.model;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.OneToOne;
import javax.persistence.Table;

@Entity
@Table(name="users")
public class User {
    
    @Id
    @GeneratedValue
    private Integer id;
    
    private String login;
    
    private String password;
    
    @OneToOne(cascade=CascadeType.ALL)
    @JoinTable(name="user_roles",
        joinColumns = {@JoinColumn(name="user_id", referencedColumnName="id")},
        inverseJoinColumns = {@JoinColumn(name="role_id", referencedColumnName="id")}
    )
    private Role role;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getLogin() {
        return login;
    }

    public void setLogin(String login) {
        this.login = login;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Role getRole() {
        return role;
    }

    public void setRole(Role role) {
        this.role = role;
    }    

}



另外 spring mvc 控制层编写就比较简单了,dao 层与 service 的编写就如同平常的程序一样,没有什么特殊的,由于提供了代码下载, 所以也就不详细介绍了。

整个应用程序运行起来后的结果如下:




这个时候,点击先关超级连接的时候,根据用户的权限不同,可能出现不同的页面或者 403 页面.

注意事项
1. 如果出现如下错误:java.lang.IllegalArgumentException: Failed to evaluate e­xpression 'ROLE_ADMIN' ,类似错误的时候,注意检查spring security配置:  <http auto-config="true">     , 还有一种配置方式是使用spring spel表达式:
程序代码 程序代码

<http auto-config="true" use-e­xpressions="true">
......    
access="hasRole('ROLE_XXX) or hasRole('ROLE_YYY')"
这种形式


2.在用hibernate4  的时候,我遇到了sessionFactory  没有办法在dao层 autowire ,自动注入的问题. 后来发现是少了javassist-3.12.1.GA.jar 这个包。

虽然这个例子还不是很完善,但应付一般的权限系统,应该可以了,如果有时间,还需要加强,至少把 资源的管理也纳入数据中,这样就比较完善了。

整个代码下载, 省略了jar 包,太大了,没空间. 可以参考 我前面提供的 jar 清单.

下载文件 spring mvc, spring security,hibernate,mysql 例子下载.


除非申明,文章均为一号门原创,转载请注明本文地址,谢谢!
[本日志由 轻舞肥羊 于 2013-12-21 10:34 PM 编辑]
文章来自: 本站原创
引用通告: 查看所有引用 | 我要引用此文章
Tags: spring security hibernate mysql
相关日志:
评论: 0 | 引用: 0 | 查看次数: -
发表评论
昵 称:
密 码: 游客发言不需要密码.
内 容:
验证码: 验证码
选 项:
虽然发表评论不用注册,但是为了保护您的发言权,建议您注册帐号.