首先,我们要明确认证的流程:
1. 获取当前的 Subject. 调用 SecurityUtils.getSubject();
2. 测试当前的用户是否已经被认证. 即是否已经登录. 调用 Subject 的 isAuthenticated()
3. 若没有被认证, 则把用户名和密码封装为 UsernamePasswordToken 对象
1). 创建一个表单页面
2). 把请求提交到 SpringMVC 的 Handler
3). 获取用户名和密码.
4. 执行登录: 调用 Subject 的 login(AuthenticationToken) 方法.
5. 自定义 Realm 的方法, 从数据库中获取对应的记录, 返回给 Shiro.
1). 实际上需要继承 org.apache.shiro.realm.AuthenticatingRealm 类
2). 实现 doGetAuthenticationInfo(AuthenticationToken) 方法.
6. 由 shiro 完成对密码的比对..
密码的比对:
通过 AuthenticatingRealm 的 credentialsMatcher 属性来进行的密码的比对!
如何把一个字符串加密为 MD5
替换当前 Realm 的 credentialsMatcher 属性. 直接使用 HashedCredentialsMatcher 对象, 并设置加密算法即可.
为什么使用 MD5 盐值加密?如何做到?
如何做到:
1). 在 doGetAuthenticationInfo 方法返回值创建 SimpleAuthenticationInfo 对象的时候, 需要使用
SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName) 构造器
2). 使用 ByteSource.Util.bytes() 来计算盐值.
3). 盐值需要唯一: 一般使用随机字符串或 user id
4). 使用 new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations); 来计算盐值加密后的密码的值.
接下来,我们进行代码的编写和配置文件的相关配置
applicationContext.xml
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> 5 6 <!-- ========================================================= 7 Shiro Core Components - Not Spring Specific 8 ========================================================= --> 9 <!-- Shiro's main business-tier object for web-enabled applications 10 (use DefaultSecurityManager instead when there is no web environment)--> 11 <!-- 12 1.配置SecurityManager! 13 --> 14 <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> 15 <property name="cacheManager" ref="cacheManager"/> 16 <property name="realm" ref="jdbcRealm"/> 17 </bean> 18 19 <!-- Let's use some enterprise caching support for better performance. You can replace this with any enterprise 20 caching framework implementation that you like (Terracotta+Ehcache, Coherence, GigaSpaces, etc --> 21 22 <!-- 23 2.配置cacheManager 24 2.1 需要加入ehcache的jar包及配置文件 25 --> 26 <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"> 27 <!-- Set a net.sf.ehcache.CacheManager instance here if you already have one. If not, a new one 28 will be creaed with a default config: 29 <property name="cacheManager" ref="ehCacheManager"/> --> 30 <!-- If you don't have a pre-built net.sf.ehcache.CacheManager instance to inject, but you want 31 a specific Ehcache configuration to be used, specify that here. If you don't, a default 32 will be used.: --> 33 <property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/> 34 </bean> 35 36 <!-- Used by the SecurityManager to access security data (users, roles, etc). 37 Many other realm implementations can be used too (PropertiesRealm, 38 LdapRealm, etc. --> 39 40 <!-- 41 3.配置Realm 42 3.1直接配置实现了com.atguigu.shiro.realms.ShiroRealm接口的bean 43 --> 44 <bean id="jdbcRealm" class="com.atguigu.shiro.realms.ShiroRealm"> 45 <!-- 配置MD5加密 --> 46 <property name="credentialsMatcher"> 47 <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"> 48 <!-- 指定加密方式为MD5 --> 49 <property name="hashAlgorithmName" value="MD5"></property> 50 <!-- 指定加密次数 --> 51 <property name="hashIterations" value="1024"></property> 52 </bean> 53 </property> 54 </bean> 55 56 <!-- ========================================================= 57 Shiro Spring-specific integration 58 ========================================================= --> 59 <!-- Post processor that automatically invokes init() and destroy() methods 60 for Spring-configured Shiro objects so you don't have to 61 1) specify an init-method and destroy-method attributes for every bean 62 definition and 63 2) even know which Shiro objects require these methods to be 64 called. --> 65 <!-- 66 4.配置LifecycleBeanPostProcessor,可以自动地来调用配置在Spring IOC容器中shiro bean的生命周期方法 67 --> 68 <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> 69 70 <!-- Enable Shiro Annotations for Spring-configured beans. Only run after 71 the lifecycleBeanProcessor has run: --> 72 73 <!-- 74 5.启用IOC容器中使用shiro的注解,但必须在配置了LifecycleBeanPostProcessor之后才可以使用 75 --> 76 <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" 77 depends-on="lifecycleBeanPostProcessor"/> 78 <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> 79 <property name="securityManager" ref="securityManager"/> 80 </bean> 81 82 <!-- Define the Shiro Filter here (as a FactoryBean) instead of directly in web.xml - 83 web.xml uses the DelegatingFilterProxy to access this bean. This allows us 84 to wire things with more control as well utilize nice Spring things such as 85 PropertiesPlaceholderConfigurer and abstract beans or anything else we might need: --> 86 87 <!-- 88 6.配置ShiroFilter 89 6.1 id必须和web.xml文件中配置的DelegatingFilterProxy的 <filter-name> 一致 90 若不一致,则会抛出:NoSuchBeanDefinitionException. 因为 Shiro 会来 IOC 容器中查找和 <filter-name> 名字对应的 filter bean. 91 6.2 92 --> 93 <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> 94 <property name="securityManager" ref="securityManager"/> 95 <property name="loginUrl" value="/login.jsp"/> 96 <property name="successUrl" value="/list.jsp"/> 97 <property name="unauthorizedUrl" value="/unauthorized.jsp"/> 98 99 <!-- 100 配置哪些页面需要受保护 101 以及访问这些页面需要的权限 102 1). anon 可以被匿名访问 103 2). authc 必须认证(即登录)后才可以访问的页面 104 3). logout 登出 105 --> 106 <property name="filterChainDefinitions"> 107 <value> 108 /login.jsp = anon 109 /shiro/login = anon 110 /shiro/logout = logout 111 # everything else requires authentication: 112 /** = authc 113 </value> 114 </property> 115 </bean> 116 </beans>
ShiroHandler.java
1 package com.atguigu.shiro.handlers; 2 3 import org.apache.shiro.SecurityUtils; 4 import org.apache.shiro.authc.AuthenticationException; 5 import org.apache.shiro.authc.UsernamePasswordToken; 6 import org.apache.shiro.subject.Subject; 7 import org.springframework.stereotype.Controller; 8 import org.springframework.web.bind.annotation.RequestMapping; 9 import org.springframework.web.bind.annotation.RequestParam; 10 11 @Controller 12 @RequestMapping("/shiro") 13 public class ShiroHandler { 14 15 @RequestMapping("/login") 16 public String login(@RequestParam("username") String username, @RequestParam("password") String password) { 17 // 获取当前的 Subject. 调用 SecurityUtils.getSubject(); 18 Subject currentUser = SecurityUtils.getSubject(); 19 // 测试当前的用户是否已经被认证. 即是否已经登录. 20 // 调动 Subject 的 isAuthenticated() 21 if (!currentUser.isAuthenticated()) { 22 // 把用户名和密码封装为 UsernamePasswordToken 对象 23 UsernamePasswordToken token = new UsernamePasswordToken(username,password); 24 // rememberme 25 token.setRememberMe(true); 26 try { 27 System.out.println("1." + token.hashCode()); 28 // 执行登录. 29 currentUser.login(token); 30 } 31 // 所有认证时异常的父类. 32 catch (AuthenticationException ae) { 33 System.out.println("登录失败:"+ae.getMessage()); 34 } 35 } 36 return "redirect:/list.jsp"; 37 } 38 }
ShiroRealm.java
1 package com.atguigu.shiro.realms; 2 3 import org.apache.shiro.authc.AuthenticationException; 4 import org.apache.shiro.authc.AuthenticationInfo; 5 import org.apache.shiro.authc.AuthenticationToken; 6 import org.apache.shiro.authc.LockedAccountException; 7 import org.apache.shiro.authc.SimpleAuthenticationInfo; 8 import org.apache.shiro.authc.UnknownAccountException; 9 import org.apache.shiro.authc.UsernamePasswordToken; 10 import org.apache.shiro.crypto.hash.SimpleHash; 11 import org.apache.shiro.realm.AuthenticatingRealm; 12 import org.apache.shiro.util.ByteSource; 13 14 public class ShiroRealm extends AuthenticatingRealm { 15 16 @Override 17 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { 18 19 System.out.println("doGetAuthenticationInfo " + token.hashCode()); 20 21 //1. 把AuthenticationToken 转换为 UsernamePasswordToken 22 UsernamePasswordToken upToken = (UsernamePasswordToken)token; 23 24 //2. 从UsernamePasswordToken 中来获取username 25 String username = upToken.getUsername(); 26 27 //3.调用数据库的方法,从数据库查询username 对应的用户记录 28 System.out.println("从数据库中获取username:" + username +" 所对应的用户信息"); 29 30 //4.若用户不存在可以抛出 UnknownAccountException 异常 31 if ("unknown".equals(username)) { 32 throw new UnknownAccountException("用户不存在"); 33 } 34 35 //5.根据用户信息的情况,决定是否抛出其它的 AuthenticationException 异常 36 if ("monster".equals(username)) { 37 throw new LockedAccountException("用户被锁定"); 38 } 39 40 //6.根据用户的情况,来构建 AuthenticationToken 对象并返回,通常使用的实现类为:SimpleAuthenticationInfo 41 //一下信息是从数据库中获取的 42 //1).principals:认证的实体信息,可以是username,也可以是数据表对应的实体类对象 43 Object principal = username; 44 //2).credentials:密码 45 Object hashedCredentials = null; //"fc1709d0a95a6be30bc5926fdb7f22f4"; 46 if ("admin".equals(username)) { 47 hashedCredentials = "038bdaf98f2037b31f1e75b5b4c9b26e"; 48 }else if("user".equals(username)) { 49 hashedCredentials = "098d2c478e9c11555ce2823231e02ec1"; 50 } 51 52 53 //3).realmName:当前realm 对象的name,调用父类的getName()方法即可 54 String realmName = getName(); 55 //4).盐值 56 //SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal, credential,realmName); 57 //因为用户名唯一,使用用户名作为盐值 58 ByteSource credentialsSalt = ByteSource.Util.bytes(username); 59 SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal, hashedCredentials, credentialsSalt, realmName); 60 return info; 61 } 62 63 public static void main(String[] args) { 64 String algorithmName = "MD5"; 65 Object password = "123456"; 66 Object salt = ByteSource.Util.bytes("user"); 67 int hashIterations = 1024; 68 Object result = new SimpleHash(algorithmName, password, salt, hashIterations); 69 System.out.println(result); 70 } 71 72 }
ehcache.xml
1 <?xml version="1.0" encoding="UTF-8"?> 2 <ehcache updateCheck="false" dynamicConfig="false"> 3 <diskStore path="java.io.tmpdir"/> 4 <!--授权信息缓存--> 5 <cache name="authorizationCache" 6 maxEntriesLocalHeap="2000" 7 eternal="false" 8 timeToIdleSeconds="1800" 9 timeToLiveSeconds="1800" 10 overflowToDisk="false" 11 statistics="true"> 12 </cache> 13 <!--身份信息缓存--> 14 <cache name="authenticationCache" 15 maxEntriesLocalHeap="2000" 16 eternal="false" 17 timeToIdleSeconds="1800" 18 timeToLiveSeconds="1800" 19 overflowToDisk="false" 20 statistics="true"> 21 </cache> 22 <!--session缓存--> 23 <cache name="activeSessionCache" 24 maxEntriesLocalHeap="2000" 25 eternal="false" 26 timeToIdleSeconds="1800" 27 timeToLiveSeconds="1800" 28 overflowToDisk="false" 29 statistics="true"> 30 </cache> 31 32 <!-- 缓存半小时 --> 33 <cache name="halfHour" 34 maxElementsInMemory="10000" 35 maxElementsOnDisk="100000" 36 eternal="false" 37 timeToIdleSeconds="1800" 38 timeToLiveSeconds="1800" 39 overflowToDisk="false" 40 diskPersistent="false" /> 41 42 <!-- 缓存一小时 --> 43 <cache name="hour" 44 maxElementsInMemory="10000" 45 maxElementsOnDisk="100000" 46 eternal="false" 47 timeToIdleSeconds="3600" 48 timeToLiveSeconds="3600" 49 overflowToDisk="false" 50 diskPersistent="false" /> 51 52 <!-- 缓存一天 --> 53 <cache name="oneDay" 54 maxElementsInMemory="10000" 55 maxElementsOnDisk="100000" 56 eternal="false" 57 timeToIdleSeconds="86400" 58 timeToLiveSeconds="86400" 59 overflowToDisk="false" 60 diskPersistent="false" /> 61 62 <!-- 63 name:缓存名称。 64 maxElementsInMemory:缓存最大个数。 65 eternal:对象是否永久有效,一但设置了,timeout将不起作用。 66 timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。 67 timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。 68 overflowToDisk:当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中。 69 diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。 70 maxElementsOnDisk:硬盘最大缓存个数。 71 diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false. 72 diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。 73 memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。 74 clearOnFlush:内存数量最大时是否清除。 75 --> 76 <defaultCache name="defaultCache" 77 maxElementsInMemory="10000" 78 eternal="false" 79 timeToIdleSeconds="600" 80 timeToLiveSeconds="600" 81 overflowToDisk="false" 82 maxElementsOnDisk="100000" 83 diskPersistent="false" 84 diskExpiryThreadIntervalSeconds="120" 85 memoryStoreEvictionPolicy="LRU"/> 86 87 </ehcache>
login.jsp
1 <%@ page language="java" contentType="text/html; charset=UTF-8" 2 pageEncoding="UTF-8"%> 3 <!DOCTYPE html> 4 <html> 5 <head> 6 <meta charset="UTF-8"> 7 <title>Insert title here</title> 8 </head> 9 <body> 10 <h4>Login Page</h4> 11 <form action="shiro/login" method="post"> 12 username:<input type="text" name="username"/> 13 <br/><br/> 14 password:<input type="password" name="password"/> 15 <br/><br/> 16 <input type="submit" value="Submit"/> 17 </form> 18 </body> 19 </html>