一直想做一个管理系统,希望它简洁,能做一个demo使用。以后在研究学习的时候,可以在此基础上增加代码。我觉得权限管理系统很值得做,因为涉及关系数据库模式的设计,能学到很多东西。万事开头难,先做个简单的,以后再慢慢完善的。任何事情关键是要做,不能停留在想。
由于之前没有多少前端编程经验,所以做起前端比较吃力。之前前端使用Bootstrap,发现需要自己编写很多前端代码,虽然花费了很多时间,但是页面做起来还是很难看。后来前端选择了EasyUI,发现特别适合做管理页面,也容易上手。当然每个框架都有其局限性,后面使用多了就知道了,就是EasyUI不够灵活,但是现在先用着,以后再选择换其他的。EasyUI官网http://www.jeasyui.com/
使用主流的SpringMVC,不但功能强大,用起来比较简单方便。
SpringMVC推荐学习网站http://www.iteye.com/blogs/subjects/kaitao-springmvc
使用Spring Data JPA。这个框架真的很赞,对于简单的业务逻辑,基本不用些SQL代码。最重要的是可以根据接口名自动生成SQL代码,极大的提高了开发效率。参考http://www.ibm.com/developerworks/cn/opensource/os-cn-spring-jpa
同时也配置了Hibernate,虽然现在还没是用,但准备后期和Spring Data JPA配合是用。
选择Shiro,简单好用,容易上手。推荐学习网站http://www.iteye.com/blogs/subjects/shiro
权限模型了解http://blog.csdn.net/painsonline/article/details/7183613/
以下只是展示了部分关键代码
用户和角色是N-N的关系。
@Entity
@Table(name = "sys_user")
public class User implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; // 编号
// 这里不能用CascadeType.ALL
@ManyToMany(targetEntity = Role.class, cascade = CascadeType.MERGE, fetch = FetchType.EAGER)
@JoinTable(name = "sys_user_role", joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id") , inverseJoinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id") )
private Set roles = new HashSet<>();
@Column(unique = true)
private String username; // 用户名
private String password; // 密码
private String salt; // 加密密码的盐
private Boolean locked = Boolean.FALSE;
private String email;
@Column(name = "create_date")
@Temporal(TemporalType.TIMESTAMP)
private Date createDate = new Date();
....
@Entity
@Table(name = "sys_role")
public class Role implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; // 编号
@OneToMany(targetEntity = Permission.class, cascade = CascadeType.REFRESH, fetch = FetchType.EAGER)
@JoinTable(name = "sys_role_permission", joinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id") , inverseJoinColumns = @JoinColumn(name = "permission_id", referencedColumnName = "id", unique = true) )
private List permissions = new ArrayList<>();
private String name;
@Column(unique = true)
private String role; // 角色标识 程序中判断使用,如"admin"
private String description; // 角色描述,UI界面显示使用
权限是资源和操作的组合。也就是说一个权限意味着对一个资源的多个操作。
@Entity
@Table(name = "sys_permission")
public class Permission implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToOne(targetEntity = Resource.class, cascade = CascadeType.REFRESH, fetch = FetchType.EAGER)
@JoinColumn(name = "resource_id", referencedColumnName = "id")
private Resource resource;
@ManyToMany(targetEntity = Operation.class, cascade = CascadeType.REFRESH, fetch = FetchType.EAGER)
@JoinTable(name = "sys_permission_operation", joinColumns = @JoinColumn(name = "permission_id", referencedColumnName = "id") , inverseJoinColumns = @JoinColumn(name = "operation_id", referencedColumnName = "id") )
private Set operations = new HashSet<>();
private String name;
private String description;
@Entity
@Table(name = "sys_resource")
public class Resource implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; // 编号
private String name; // 资源名称
@Column(unique = true)
private String identity;// 资源类型
@Entity
@Table(name = "sys_operation")
public class Operation implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; // 编号
private String name; // 操作名称
@Column(unique = true)
private String operation;// 操作标识
private String description;
Spring Data可以通过解析方法名创建查询。比如方法是findByUsername(String username),则Spring Data会自动生成SQL语句select * from 表名 where username= username.所以你对于简单的操作,你只要按照Srping Data约定写出对应的方法名,Spring Data就能自动创建SQL语句,基本不需要你写SQL语句。
一般定义DAO层时,继承一个Spring Data的基本接口,比如JpaRepository,该接口能可以帮你实现增删查改的功能外,还可以帮你做分页查询和排序。
import org.springframework.data.jpa.repository.JpaRepository;
import com.myweb.entity.User;
public interface UserDao extends JpaRepository<User, Long> {
User findByUsername(String username);
}
使用时,直接注入就可以了
@Inject
private UserDao userDao;
shiro比Spring Security简单,在实现认证授权的时候,继承一个Realm类然后扩展,这里使用AuthorizingRealm。
Realm在shiro中类似数据源,认证和授权的数据就依靠它。
public class UserRealm extends AuthorizingRealm {
@Inject
private UserService userService;
@Inject
private UserDao userDao;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 授权
String username = (String) principals.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.setRoles(userService.getStringRoles(username));
authorizationInfo.setStringPermissions(userService.getStringPermissions(username));
return authorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)
throws AuthenticationException {
// 认证
String username = (String) authenticationToken.getPrincipal();
User user = userDao.findByUsername(username);
// 查出是否有此用户
if (user == null) {
throw new UnknownAccountException();// 没找到帐号
}
if (Boolean.TRUE.equals(user.getLocked())) {
throw new LockedAccountException(); // 帐号锁定
}
// 交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配,如果觉得人家的不好可以自定义实现
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user.getUsername(),
user.getPassword(), ByteSource.Util.bytes(user.getCredentialsSalt()), getName());
return authenticationInfo;
}
}
需要SecurityUtils返回一个Subject,然后通过用户名和密码创建Token,使用这个Token登录验证。Subject 是 Shiro 的核心对象,基本所有身份验证、授权都是通过 Subject 完成。
@RequestMapping(value = "/login", method = RequestMethod.POST)
public ModelAndView login(User user) {
ModelAndView mv = new ModelAndView();
try {
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken authenticationToken = new UsernamePasswordToken(user.getUsername(),
user.getPassword());
subject.login(authenticationToken);
} catch (AuthenticationException e) {
e.printStackTrace();
mv.setViewName("redirect:/unauthorized");
return mv;
}
mv.setViewName("redirect:/index");
return mv;
}
采用注解形式,简单方便
@RequiresPermissions("user:create")
@RequestMapping(value = "/save", method = RequestMethod.POST)
@ResponseBody
public String saveUser(@RequestParam String username, @RequestParam String password, @RequestParam String email,
@RequestParam(value = "roles", required = false) List roleIds) {
User user = new User();
user.setUsername(username);
user.setPassword(password);
user.setEmail(email);
List roles = roleDao.findAll(roleIds);
user.setRoles(new HashSet<>(roles));
userService.save(user);
return "OK";
}
支持注解需要配置
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor" />
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager" />
bean>
web.xml:
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0">
<display-name>mywebdisplay-name>
<welcome-file-list>
<welcome-file>/WEB-INF/jsp/login.jspwelcome-file>
welcome-file-list>
<context-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath*:config/spring-*.xmlparam-value>
context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
listener>
<filter>
<filter-name>encodingFilterfilter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilterfilter-class>
<init-param>
<param-name>encodingparam-name>
<param-value>UTF-8param-value>
init-param>
<init-param>
<param-name>forceEncodingparam-name>
<param-value>trueparam-value>
init-param>
filter>
<filter-mapping>
<filter-name>encodingFilterfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
<filter>
<filter-name>openSessionfilter-name>
<filter-class>org.springframework.orm.hibernate4.support.OpenSessionInViewFilterfilter-class>
<init-param>
<param-name>singleSessionparam-name>
<param-value>trueparam-value>
init-param>
<init-param>
<param-name>flushModeparam-name>
<param-value>AUTOparam-value>
init-param>
filter>
<filter-mapping>
<filter-name>openSessionfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
<servlet>
<description>mywebdescription>
<display-name>mywebdisplay-name>
<servlet-name>springMVCservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath*:config/spring-mvc.xmlparam-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>springMVCservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>
<filter>
<filter-name>shiroFilterfilter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxyfilter-class>
<init-param>
<param-name>targetFilterLifecycleparam-name>
<param-value>trueparam-value>
init-param>
filter>
<filter-mapping>
<filter-name>shiroFilterfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
web-app>
spring-mvc.xml:
<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"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/data/jpa
http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
<mvc:annotation-driven />
<mvc:resources mapping="/static/**" location="/WEB-INF/static/" />
<mvc:resources mapping="/html/**" location="/WEB-INF/html/" />
<context:annotation-config />
<context:component-scan base-package="com.myweb.controller" />
<context:component-scan base-package="com.myweb.dao.imp" />
<context:component-scan base-package="com.myweb.service.imp" />
<context:component-scan base-package="com.myweb.security.util" />
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="configLocation" value="classpath:config/hibernate.cfg.xml" />
<property name="packagesToScan" value="com.myweb.entity" />
bean>
<bean id="hibernateTemplate" class="org.springframework.orm.hibernate4.HibernateTemplate">
<property name="sessionFactory" ref="sessionFactory" />
bean>
<bean id="txManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
bean>
<aop:config>
<aop:pointcut id="allMethods" expression="execution(* com.myweb.service.imp.*.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="allMethods" />
aop:config>
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="find*" propagation="REQUIRED" read-only="true" />
<tx:method name="add*" propagation="REQUIRED" />
<tx:method name="del*" propagation="REQUIRED" />
<tx:method name="update*" propagation="REQUIRED" />
<tx:method name="save*" propagation="REQUIRED" />
tx:attributes>
tx:advice>
<tx:annotation-driven />
<bean id="defaultViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" />
bean>
<jpa:repositories base-package="com.myweb.dao" repository-impl-postfix="Impl" entity-manager-factory-ref="entityManagerFactory" transaction-manager-ref="transactionManager">
jpa:repositories>
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="packagesToScan" value="com.myweb.entity" />
<property name="persistenceProvider">
<bean class="org.hibernate.ejb.HibernatePersistence" />
property>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="generateDdl" value="false" />
<property name="database" value="MYSQL" />
<property name="databasePlatform" value="org.hibernate.dialect.MySQL5InnoDBDialect" />
<property name="showSql" value="true" />
bean>
property>
<property name="jpaDialect">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect" />
property>
<property name="jpaPropertyMap">
<map>
<entry key="hibernate.query.substitutions" value="true 1, false 0" />
<entry key="hibernate.default_batch_fetch_size" value="16" />
<entry key="hibernate.max_fetch_depth" value="2" />
<entry key="hibernate.generate_statistics" value="true" />
<entry key="hibernate.bytecode.use_reflection_optimizer" value="true" />
<entry key="hibernate.cache.use_second_level_cache" value="false" />
<entry key="hibernate.cache.use_query_cache" value="false" />
map>
property>
bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
bean>
<bean name="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName">
<value>com.mysql.jdbc.Drivervalue>
property>
<property name="url">
<value>jdbc:mysql://localhost:3306/mywebvalue>
property>
<property name="username">
<value>rootvalue>
property>
<property name="password" value="jiangyu" />
bean>
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="messageConverters">
<list>
<ref bean="mappingJacksonHttpMessageConverter" />
list>
property>
bean>
<bean id="mappingJacksonHttpMessageConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>apolication/json; charset=UTF-8value>
list>
property>
bean>
<mvc:annotation-driven>
<mvc:argument-resolvers>
<bean class="com.myweb.controller.CurrentUserMethodArgumentResolver" />
mvc:argument-resolvers>
mvc:annotation-driven>
beans>
spring-shiro-web.xml:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:util="http://www.springframework.org/schema/util" 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.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="md5" />
<property name="hashIterations" value="2" />
<property name="storedCredentialsHexEncoded" value="true" />
bean>
<bean id="userRealm" class="com.myweb.security.realm.UserRealm">
<property name="credentialsMatcher" ref="credentialsMatcher" />
bean>
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="userRealm" />
bean>
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" />
<property name="loginUrl" value="/api/user/login.page" />
<property name="successUrl" value="/index" />
<property name="unauthorizedUrl" value="/unauthorized" />
<property name="filterChainDefinitions">
<value>
/static/** = anon
/api/user/login = anon
/api/user/register* = anon
/unauthorized = anon
/** = authc
value>
property>
bean>
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor" />
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager" />
bean>
beans>
hibernate.cfg.xml
<hibernate-configuration>
<session-factory>
<property name="hibernate.dialect">
org.hibernate.dialect.MySQLDialect
property>
<property name="hibernate.connection.driver_class">
com.mysql.jdbc.Driver
property>
<property name="hibernate.connection.url">
jdbc:mysql://localhost/myweb
property>
<property name="hibernate.connection.username">
root
property>
<property name="hibernate.connection.password">
jiangyu
property>
<property name="hibernate.temp.use_jdbc_metadata_defaults">falseproperty>
<property name="hbm2ddl.auto">updateproperty>
<property name="show_sql">trueproperty>
<property name="format_sql">trueproperty>
<property name="jdbc.fetch_size">50property>
<property name="jdbc.batch_size">30property>
<property name="hibernate.connection.provider_class">org.hibernate.connection.C3P0ConnectionProviderproperty>
<property name="hibernate.c3p0.acquireRetryAttempts">30property>
<property name="hibernate.c3p0.acquireIncrement">2property>
<property name="hibernate.c3p0.checkoutTimeout">30000property>
<property name="hibernate.c3p0.idleConnectionTestPeriod">120property>
<property name="hibernate.c3p0.maxIdleTime">180property>
<property name="hibernate.c3p0.initialPoolSize">3property>
<property name="hibernate.c3p0.maxPoolSize">50property>
<property name="hibernate.c3p0.minPoolSize">1property>
<property name="hibernate.c3p0.maxStatements">0property>
session-factory>
hibernate-configuration>
超级管理员用户名/密码:admin/123456。
数据库导入文件:/myweb/src/main/resources/SQL/myweb.sql
源码:https://github.com/Jdoing/myweb