1) 表名:用户表(Sys_Users)
Salt:盐(佐料)。为避免被黑客等进行攻击(暴力密码破解),所以一般在注册用户信息时,系统会随机生成一个随机码。在验证时会将密码和随机码进行运算,以验证密码是否正确。
2) 表名:角色表(Sys_Role)
3) 表名:用户角色表(Sys_User_Role)
4) 表名:资源表(Sys_Resource)
5) 表名:角色权限表(Sys_Role_Permission)
RBAC:Role Basic Access Controll(基于角色的权限控制)
说明:权限验证的基本设计是5张表,扩展后可以设计为12张表
1) 导入jar包
常用的数据库连接池:
DBCP 、C3P0、BoneCP、Proxool、DDConnectionBroker、DBPool、XAPool、Primrose、SmartPool、MiniConnectionPoolManager及Druid等
Druid:
Druid连接池最大的优势是可以监控Sql语句的执行,为后期Sql语句的优化提供帮助。
2) 配置web.xml
1 <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"> 2 <!-- 1、过滤器 --> 3 <!-- characterEncodingFilter --> 4 <!-- 编码过滤器,对所有请求和响应进行编码处理 --> 5 <filter> 6 <filter-name>CharacterEncodingFilter</filter-name> 7 <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> 8 <init-param> 9 <param-name>encoding</param-name> 10 <param-value>utf-8</param-value> 11 </init-param> 12 </filter> 13 <filter-mapping> 14 <filter-name>CharacterEncodingFilter</filter-name> 15 <url-pattern>/*</url-pattern> 16 </filter-mapping> 17 18 <!-- HiddenHttpMethodFilter --> 19 <!-- 20 响应只支持get和post,通过HiddenHttpMethodFilter可以将请求方式转为标准请求方式(解决put和delete不支持的问题)。 21 如果使用ResultFul必须配置 22 --> 23 <filter> 24 <filter-name>HiddenHttpMethodFilter</filter-name> 25 <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class> 26 </filter> 27 <filter-mapping> 28 <filter-name>HiddenHttpMethodFilter</filter-name> 29 <!-- 对所有DispaccherServlet分配的任务进行过滤 --> 30 <servlet-name>springDispatcherServlet</servlet-name> 31 </filter-mapping> 32 33 <!-- 34 在web中使用shiro必须配置DelegatingFilterProxy过滤器(实现了Filter接口的Bean对象都会被DelegatingFilterProxy所代理) 35 targetFilterLifecycle:调用过滤器的init()和destroy()方法 36 注意: 37 filte-name的属性名必须和shiro中bean的Id相一致 38 --> 39 <filter> 40 <filter-name>shiroFilter</filter-name> 41 <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> 42 <init-param> 43 <param-name>targetFilterLifecycle</param-name> 44 <param-value>true</param-value> 45 </init-param> 46 </filter> 47 <filter-mapping> 48 <filter-name>shiroFilter</filter-name> 49 <url-pattern>/*</url-pattern> 50 </filter-mapping> 51 <!-- 2、监听器 --> 52 <!-- ContextLoaderListener --> 53 <!-- 54 用于监听web容器的启动,当web容器启动时自动对ApplicatoinContext信息进行装配 55 监听器实现了ServletContextListener接口 56 --> 57 <listener> 58 <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> 59 </listener> 60 <context-param> 61 <param-name>contextConfigLocation</param-name> 62 <!-- 可以去配置多个value值,各值之间使用","进行分隔 --> 63 <param-value> 64 classpath:applicationContext.xml 65 </param-value> 66 </context-param> 67 68 <!-- 3、Servlet --> 69 <!-- DispatcherServlet --> 70 <!-- 多所有请求进行拦截,然后根据请求资源的类型进行任务的分派 --> 71 <servlet> 72 <servlet-name>springDispatcherServlet</servlet-name> 73 <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> 74 <init-param> 75 <param-name>contextConfigLocation</param-name> 76 <param-value>classpath:spring-mvc.xml</param-value> 77 </init-param> 78 <load-on-startup>1</load-on-startup> 79 </servlet> 80 <servlet-mapping> 81 <servlet-name>springDispatcherServlet</servlet-name> 82 <url-pattern>/</url-pattern> 83 </servlet-mapping> 84 85 86 </web-app>
说明:
folder就是普通的文件夹,它和我们window下面使用的文件夹没有任何区别
source folder文件夹是一种特别的文件夹,是folder的子集,source folder中的代码会被编译且存放在web-inf/classes下
package也是特殊的文件夹,package是一种物理路径。通过"."区分上下级
3) 配置applicationContext.xml
1 <beans xmlns="http://www.springframework.org/schema/beans" 2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> 4 <!-- 配置Shiro的核心组件(用于自动注入SecuriyManager) --> 5 <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> 6 <!-- 1、配置缓存管理器 --> 7 <property name="cacheManager" ref="cacheManager"/> 8 <!-- 配置realm --> 9 <property name="realm" ref="realm"/> 10 </bean> 11 12 <!-- 1)、配置缓存管理器:对会话的缓存进行管理 --> 13 <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"> 14 <property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/> 15 </bean> 16 17 <!-- 2)、配置Realm:实际进行验证的类 --> 18 <bean id="realm" class="cn.hl.realm.UserRealm"> 19 </bean> 20 21 <!-- 2、 自动调用过滤器的init()和destroy()方法--> 22 <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> 23 24 <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" 25 depends-on="lifecycleBeanPostProcessor"/> 26 <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> 27 <property name="securityManager" ref="securityManager"/> 28 </bean> 29 30 <!-- 3、定义过滤器(配置一组过滤器) --> 31 <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> 32 <property name="securityManager" ref="securityManager"/> 33 <!-- 配置登录资源路径位置 --> 34 <property name="loginUrl" value="/login.jsp"/> 35 <!-- 配置登录成功跳转的资源路径位置(一般不需要配置。一般都是通过请求处理方法进行处理) --> 36 <!-- 37 <property name="successUrl" value="/index.jsp"/> 38 --> 39 <!-- 配置无权限的跳转的资源路径位置 --> 40 <property name="unauthorizedUrl" value="/unauthorized.jsp"/> 41 <!-- 42 配置其它资源的访问控制 43 常用内置过滤器对象 44 anon :允许匿名访问 45 authc :需要通过认证才能访问 46 roles :需要具体角色才能访问 47 user :指定用户才能访问(较少使用) 48 logout :注销当前用户 49 --> 50 <property name="filterChainDefinitions"> 51 <value> 52 /login.jsp=anon 53 /unauthorized.jsp=anon 54 /login =anon 55 /logout=logout 56 57 # 其余资源需要经过认证后才能访问 58 /** = authc 59 </value> 60 </property> 61 </bean> 62 63 </beans>
4) 配置spring-mvc.xml
1 <beans xmlns="http://www.springframework.org/schema/beans" 2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xmlns:context="http://www.springframework.org/schema/context" 4 xmlns:mvc="http://www.springframework.org/schema/mvc" 5 xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd 6 http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd 7 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd"> 8 <!-- 设置静态资源的访问控制 --> 9 <mvc:default-servlet-handler/> 10 11 <!-- 启用注解 --> 12 <mvc:annotation-driven></mvc:annotation-driven> 13 14 <!-- 配置自动扫描 --> 15 <context:component-scan base-package="cn.hl.controller"></context:component-scan> 16 17 <!-- 配置视图解析器 --> 18 <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> 19 <property name="prefix" value="/"></property> 20 <property name="suffix" value=".jsp"></property> 21 </bean> 22 </beans>
5) 编写Controller
1 @Controller 2 public class UserController { 3 @RequestMapping("login") 4 public String login(String account, String pwd){ 5 System.out.println(SecurityUtils.getSubject()); 6 System.out.println("account = " + account+" | pwd=" + pwd); 7 //执行登录验证 8 Subject subject = SecurityUtils.getSubject(); 9 UsernamePasswordToken token = new UsernamePasswordToken(account,pwd); 10 11 //判断用户是否已经登录(如果没有登录则执行登录验证) 12 if(!subject.isAuthenticated()){ 13 try{ 14 //执行登录验证 15 subject.login(token); 16 } 17 catch(AuthenticationException ex){ 18 System.out.println("提示:" + ex.getMessage()); 19 return "redirect:/login.jsp"; 20 } 21 } 22 //已经登录则跳转到"index.jsp"页面 23 return "index"; 24 } 25 }
6) 编写Realm
1 public class UserRealm extends AuthenticatingRealm{ 2 @Override 3 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { 4 //1、获取用户信息(直接使用参数对象的Token需要进行转换) 5 //String username = String.valueOf(token.getPrincipal()); 6 7 //2、将Token直接转换为UsernamePasswordToken(可以避免转换) 8 UsernamePasswordToken userToken = (UsernamePasswordToken)token; 9 String username = userToken.getUsername(); 10 11 //执行登录验证 12 System.out.println("通过数据库读取帐号信息"); 13 //从数据库中读取到的内容 14 String user="admin"; 15 16 //对账户名进行验证 17 //User user = service.getUserByAccount(username); 18 if(!user.equals(username)){ 19 throw new UnknownAccountException("帐号信息不存在"); 20 } 21 //数据库中读取到的密码(String credentials = user.getPassword()) 22 String credentials = "123"; 23 24 //对用户进行验证 25 SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, credentials, getName()); 26 return info; 27 } 28 }
盐
- 默认密码验证方式(直接进行比较)
1 [SimpleCredentialsMatcher] 2 public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) { 3 //获取票证中的密码(用户提交的) 4 Object tokenCredentials = getCredentials(token); 5 //默认的原始密码(数据库中提取的) 6 Object accountCredentials = getCredentials(info); 7 return equals(tokenCredentials, accountCredentials); 8 }
- 缺点
² 同一个字符串加密加密后的结果是不变的
² 密码容易被破解
- 解决方案
在密码基础上加点"盐",加盐后可以使得密码破解的速度降低.
- 密码生成方式
1 //加密算法 2 String algorithmName ="MD5"; 3 //加密次数 4 int hashIterations=1024; 5 6 //定义盐 7 ByteSource salt = ByteSource.Util.bytes("hl"); 8 9 //向数据库中存密码时,使用如下方式进行存储 10 //存储时可以考虑使用账号(+其他字段作为盐) 11 Object obj = new SimpleHash(algorithmName, "123", salt, hashIterations);
加入盐的验证
1 public class UserRealm extends AuthenticatingRealm{ 2 3 @Override 4 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { 5 //1、获取用户信息(直接使用参数对象的Token需要进行转换) 6 //String username = String.valueOf(token.getPrincipal()); 7 8 //2、将Token直接转换为UsernamePasswordToken(可以避免转换) 9 UsernamePasswordToken userToken = (UsernamePasswordToken)token; 10 String username = userToken.getUsername(); 11 12 //执行登录验证 13 System.out.println("通过数据库读取帐号信息"); 14 //从数据库中读取到的内容 15 String user="admin"; 16 17 //对账户名进行验证 18 //User user = service.getUserByAccount(username); 19 if(!user.equals(username)){ 20 throw new UnknownAccountException("帐号信息不存在"); 21 } 22 //数据库中读取到的密码(String credentials = user.getPassword()) 23 //String credentials = "123"; 24 //数据库中读取到的密码(数据库中存储的密码) 25 Object hashedCredentials= "e3076e0bf31c666de1bac66b6bbb35d6"; 26 27 //盐 28 String salt = "hl"; 29 ByteSource credentialsSalt = ByteSource.Util.bytes(salt.getBytes()); 30 31 32 //e3076e0bf31c666de1bac66b6bbb35d6 33 //对用户进行验证(默认的匹配方式是直接进行比较) 34 //SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, credentials, getName()); 35 SimpleAuthenticationInfo info = new SimpleAuthenticationInfo( 36 username, 37 hashedCredentials, 38 credentialsSalt, 39 getName()); 40 return info; 41 } 42 }
【自定义角色】
1) 配置
1 <property name="filterChainDefinitions"> 2 <value> 3 /login.jsp=anon 4 /unauthorized.jsp=anon 5 /login =anon 6 /logout=logout 7 8 /add.jsp=roles[admin] 9 /update.jsp=roles["user,admin"] 10 /show.jsp=user[root] 11 12 # 其余资源需要经过认证后才能访问 13 /** = authc 14 </value> 15 </property>
2) 授权
1 /** 2 * 不需要授权则直接继承类:AuthenticatingRealm 3 * 需要授权则需要继承类:AuthorizingRealm 4 * @author Terry 5 * 6 */ 7 public class UserRealm extends AuthorizingRealm{ 8 9 /** 10 * 执行登录验证 11 */ 12 @Override 13 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { 14 System.out.println("1"); 15 //1、获取用户信息(直接使用参数对象的Token需要进行转换) 16 //String username = String.valueOf(token.getPrincipal()); 17 18 //2、将Token直接转换为UsernamePasswordToken(可以避免转换) 19 UsernamePasswordToken userToken = (UsernamePasswordToken)token; 20 String username = userToken.getUsername(); 21 22 //执行登录验证 23 System.out.println("通过数据库读取帐号信息"); 24 //从数据库中读取到的内容 25 String user="admin"; 26 27 //对账户名进行验证 28 //User user = service.getUserByAccount(username); 29 if(username==null){ 30 throw new UnknownAccountException("帐号信息不存在"); 31 } 32 33 //数据库中读取到的密码(String credentials = user.getPassword()) 34 //String credentials = "123"; 35 //数据库中读取到的密码(数据库中存储的密码) 36 Object hashedCredentials= "e3076e0bf31c666de1bac66b6bbb35d6"; 37 38 //盐 39 String salt = "hl"; 40 ByteSource credentialsSalt = ByteSource.Util.bytes(salt.getBytes()); 41 42 43 //e3076e0bf31c666de1bac66b6bbb35d6 44 //对用户进行验证(默认的匹配方式是直接进行比较) 45 //SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, credentials, getName()); 46 SimpleAuthenticationInfo info = new SimpleAuthenticationInfo( 47 username, 48 hashedCredentials, 49 credentialsSalt, 50 getName()); 51 return info; 52 } 53 54 /** 55 * 执行授权操作 56 * @param principals 57 * @return 58 */ 59 @Override 60 protected AuthorizationInfo doGetAuthorizationInfo( 61 PrincipalCollection principals) { 62 Set<String> roles=new HashSet<String>(); 63 64 System.out.println("授权到用户......"); 65 66 String username= String.valueOf(principals.getPrimaryPrincipal()); 67 68 //从数据库中为不同用户读取角色 69 if("admin".equals(username)){ 70 roles.add("admin"); 71 roles.add("user"); 72 } 73 else if("sa".equals(username)){ 74 roles.add("user"); 75 } 76 77 SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); 78 //为用户设置角色(授权) 79 info.setRoles(roles); 80 return info; 81 } 82 }
【shiro标签】
1) 引入标签库
1 <%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
2) 应用标签库
1 <!-- 包含指定角色时显示元素向,否则将隐藏 --> 2 <shiro:hasRole name="admin"> 3 <a href="add.jsp">add page</a> 4 </shiro:hasRole>