一、数据库设计:
数据库有三张表,分别是tb_user用户表,tb_role角色表,tb_permission权限表。
1、tb_user
设置外键rid关联tb_role表
2、tb_role
3、tb_permission
设置外键关联tb_role表
二、项目环境搭建:
1、新建maven web app,结构如下:
2、接下来就是添加依赖:
<dependencies>
<!-- junit测试 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <!-- 添加Servlet支持 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>2.3.1</version> </dependency> <!-- 添加jtl支持 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <!-- 添加Spring支持 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>4.3.14.RELEASE</version> <scope>test</scope> </dependency> <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2</version> </dependency> <!-- mybatis依赖 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.3.0</version> </dependency> <!-- 添加日志支持 --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.12</version> </dependency> <!-- 数据库驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.37</version> </dependency> <!-- c3p0数据源 --> <dependency> <groupId>com.mchange</groupId> <artifactId>c3p0</artifactId> <version>0.9.5.2</version> </dependency> <!-- shiro相关 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.2.4</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>1.2.4</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.2.4</version> </dependency> </dependencies>
依赖对应的作用在代码中有简要的注释。
3、完成配置文件:
在spring文件夹下有:
spring-dao.xml,
spring-mvc.xml,
spring-service.xml,
spring-shiro.xml
①spring-dao.xml
<!-- 配置整合mybatis过程 --> <!-- 1、配置数据库相关参数properties的属性:${url} --> <context:property-placeholder location="classpath:jdbc.properties" /> <!-- 2、配置数据库连接池 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <!-- 配置连接池属性 --> <property name="driverClass" value="${jdbc.driver}" /> <property name="jdbcUrl" value="${jdbc.url}" /> <property name="user" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> <!-- c3p0连接池的私有属性 --> <property name="maxPoolSize" value="30" /> <property name="minPoolSize" value="10" /> <!-- 关闭连接不自动commit --> <property name="autoCommitOnClose" value="false" /> <!-- 获取连接超时时间 --> <property name="checkoutTimeout" value="10000" /> <!-- 当获取连接失败时重试次数 --> <property name="acquireRetryAttempts" value="2" /> </bean> <!-- 3、配置mybatis的sqlSessionFactory --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <!-- 自动扫描mappers.xml文件 --> <property name="mapperLocations" value="classpath:mappers/*.xml" /> <!-- mybatis配置文件 --> <property name="configLocation" value="classpath:mybatis-config.xml" /> </bean> <!-- 4、DAO接口所在包名,Spring会自动查找其下的类 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.zhu.shiroweb.dao" /> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property> </bean>
②spring-service.xml
<!-- 自动扫描 --> <context:component-scan base-package="com.zhu.shiroweb.service" />
③spring-mvc.xml
<!-- 配置springmvc --> <!-- 1、开启springMvc注解模式 --> <mvc:annotation-driven /> <!-- 2、视图解析器 --> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/" /> <property name="suffix" value=".jsp"></property> </bean> <!-- 3、开启Shiro注解 --> <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> <!-- 4、扫描web相关的bean --> <context:component-scan base-package="com.zhu.shiroweb.controller"/>
注意:开启shiro注解的配置要写在spring-mvc.xml中,具体原因我也不太清楚,有知道的大佬还请留言指教哦!
④spring-shiro.xml
<!-- 1、将自定义Realm加入IOC容器 --> <bean id="myRealm" class="com.zhu.shiroweb.realm.MyRealm"> <!-- 配置MD5加密,若不进行MD5加密,这段代码不用 --> <property name="credentialsMatcher"> <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"> <!-- MD5加密 --> <property name="hashAlgorithmName" value="MD5"/> <!-- 加密次数 --> <property name="hashIterations" value="1024"/> </bean> </property> <!-- 配置MD5加密,若不进行MD5加密,这段代码不用 --> </bean> <!-- 2、配置安全管理器 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="myRealm"/> </bean> <!-- 3、配置Shiro过滤器,id名必须和web.xml中的过滤器名一致 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <!-- Shiro的核心安全接口,这个属性是必须的 --> <property name="securityManager" ref="securityManager"/> <!-- 身份认证失败,则跳转到登录页面的配置 --> <property name="loginUrl" value="/login.jsp"/> <!-- 权限认证失败,则跳转到指定页面 --> <property name="unauthorizedUrl" value="/unauthor.jsp"/> <!-- Shiro连接约束配置,即过滤链的定义 --> <property name="filterChainDefinitions"> <value> /login=anon /admin/**=authc,roles[admin] </value> </property> </bean> <!-- 4、保证实现了Shiro内部lifecycle函数的bean执行 --> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
以下配置文件在resources根目录:
⑤jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql:///#?useUnicode=true&characterEncoding=utf8 jdbc.username=# jdbc.password=#
⑥log4j.properties
log4j.rootLogger=DEBUG, Console #Console log4j.appender.Console=org.apache.log4j.ConsoleAppender log4j.appender.Console.layout=org.apache.log4j.PatternLayout log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n log4j.logger.java.sql.ResultSet=INFO log4j.logger.org.apache=INFO log4j.logger.java.sql.Connection=DEBUG log4j.logger.java.sql.Statement=DEBUG log4j.logger.java.sql.PreparedStatement=DEBUG
⑦mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!-- 别名 --> <typeAliases> <package name="com.zhu.shiroweb.entity"/> </typeAliases> </configuration>
web.xml在WEB-INF目录下:
⑧web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1" metadata-complete="true"> <display-name>shiroweb</display-name> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> <!-- Spring配置文件 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/spring-*.xml</param-value> </context-param> <!-- 添加对springmvc的支持 --> <servlet> <servlet-name>springMVC</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/spring-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> <async-supported>true</async-supported> </servlet> <servlet-mapping> <servlet-name>springMVC</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <!-- Spring监听器 --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- shiro过滤器定义 --> <filter> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <init-param> <!-- 该值缺省为false,表示生命周期由SpringApplicationContext管理,设置为true则表示由ServletContainer管理 --> <param-name>targetFilterLifecycle</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>shiroFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 编码过滤器,非必须 --> <filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <async-supported>true</async-supported> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>
至此已完成所有配置,所有的配置文件核心代码都写了注释。
三、项目功能实现
登录验证:
项目功能描述:
在数据库中有两个用户,一个tom,角色为admin,对应的权限有create,delete,query和update,另一个用户cat,角色为guest,权限只有create和query。
1、首先新建User实体类(set、get方法略):
User.java
public class User { private Integer uid; private String userName; private String password; }
2、dao层的开发
UserDao.java
public interface UserDao { /** * 根据用户名查询用户 * @param userName * @return */ public User getByUserName(String userName); /** * 根据用户名查询角色 * @param userName * @return */ public Set<String> getRoles(String userName); /** * 根据用户名查询权限 * @param userName * @return */ public Set<String> getPermissions(String userName); }
UserDao.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.zhu.shiroweb.dao.UserDao"> <resultMap type="com.zhu.shiroweb.entity.User" id="UserResult"> <result property="uid" column="uid"/> <result property="userName" column="user_name"/> <result property="password" column="pass_word"/> </resultMap> <select id="getByUserName" parameterType="String" resultMap="UserResult"> select * from tb_user where user_name=#{userName} </select> <select id="getRoles" parameterType="String" resultType="String"> select r.role_name from tb_user u,tb_role r where u.rid=r.rid and u.user_name=#{userName} </select> <select id="getPermissions" parameterType="String" resultType="String"> select p.permission_name from tb_user u,tb_role r,tb_permission p where u.rid=r.rid and p.rid=r.rid and u.user_name=#{userName} </select> </mapper>
注意:这里并没有role和permission对应的实体类,也可以新建其对应的实体类,然后把他们设置为User的成员变量,用List集合装载。但是这样做更麻烦一点,因为等下shiro要用到的就是Set集合,如果是List等下还需做转换。
3、dao层测试
写到这可以做一下junit测试,由于篇幅原因,此处不再赘述。
4、service层开发
由于并没有增加逻辑,只是简单调用dao层,所以不再说明。
5、自定义realm
MyRealm.java
public class MyRealm extends AuthorizingRealm { @Autowired private UserService userService; /** * 为登录用户授予权限和角色 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String userName = (String) principals.getPrimaryPrincipal(); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); authorizationInfo.setRoles(userService.getRoles(userName)); authorizationInfo.setStringPermissions(userService.getPermissions(userName)); return authorizationInfo; } /** * 验证当前登录用户 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { String userName = (String) token.getPrincipal(); User user = userService.getByUserName(userName); //使用md5加密 //当前realm对象的name String realmName = getName(); //盐值 ByteSource credenttialsSalt = ByteSource.Util.bytes(user.getUserName()); //封装用户信息,构建AuthenticationInfo对象并返回 AuthenticationInfo authcInfo = new SimpleAuthenticationInfo(user.getUserName(), user.getPassword(), credenttialsSalt, realmName); return authcInfo; //使用md5加密 //不加密 /* * if (user != null) { * AuthenticationInfo authcInfo = new * SimpleAuthenticationInfo(user.getUserName(), user.getPassword(), "xx"); * return authcInfo; * } else { * return null; * } */ //不加密 }
这个类就验证登录和为用户授权两个方法,代码中已有注释说明。若不使用MD5加密,则把我写了MD5加密注释之间那段代码注释掉,把下面注释放开就行;若要使用MD5加密,数据库中的密码也得是加密后的密码可以通过下面的方法获取明文密码对应的密文密码。
获取密文的方法:
public static void main(String[] args) { String hashAlgorithName = "MD5"; String password = "5678"; int hashIterations = 1024; ByteSource credentialsSalt = ByteSource.Util.bytes("cat"); Object obj = new SimpleHash(hashAlgorithName, password, credentialsSalt, hashIterations); System.out.println(obj); }
6、用户登录的Controller
@Controller @RequestMapping("/user") public class UserController { /** * * @param user * @param request * @return */ @RequestMapping("/login") public String login(User user,HttpServletRequest request){ //获取当前登录用户 Subject subject=SecurityUtils.getSubject(); //封装表单中提交的用户名和密码 UsernamePasswordToken token=new UsernamePasswordToken(user.getUserName(), user.getPassword()); try{ //调用login方法,传入封装好的token subject.login(token); //登录成功跳转success.jsp return "redirect:/success.jsp"; }catch(Exception e){ e.printStackTrace(); //登录失败就重新登录 request.setAttribute("errorMsg", "登录失败"); return "login"; } } }
7、login.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <form action="${pageContext.request.contextPath }/user/login" method="post"> userName:<input type="text" name="userName" value="${user.userName }"/><br/> password:<input type="password" name="password" value="${user.password }"><br/> <input type="submit" value="login"/><font color="red">${errorMsg }</font> </form> </body> </html>
过程梳理:
在controller中获取到前端表单输入的用户名和密码,封装到token中,然后调用subject的login的方法,传入这个token,这个token就会把数据带到realm中,realm中再调service层查询,根据前端token中携带的用户名去查询数据库中记录,进行密码比对。
这里解释spring-shiro.xml中/login=anon
的作用,这行代码的意思就是不拦截登录方法,可以匿名访问。
以上就是验证登录的整个流程。