第一章 Shiro简介
第1节 shiro介绍
1 | 官网地址: http://shiro.apache.org/ |
Apache Shiro 是一个功能强大,易于使用的Java安全框架,他执行认证、授权、加密、会话管理等功能,使用Shiro易于理解的API,使你能够轻松的保护任何应用,如移动端应用,大型web应用以及企业级应用.
Shiro可以非常容易的开发出足够好的应用,不仅可以用在JavaSE环境,也可以用在JavaEE环境
第2节 整体功能图
- Authentication[ɔːˌθentɪˈkeɪʃn]:身份认证 / 登录,验证用户是不是拥有相应的身份
- Authorization[ˌɔːθəraɪˈzeɪʃn]:授权,即权限验证,验证某个已认证的用户是否拥有某个权限
- Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中
- Cryptography[krɪpˈtɒɡrəfi]:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储
- Web Support:Web 支持,可以非常容易的集成到 Web 环境
- Caching:缓存,比如用户登录后,其用户信息、拥有的角色 / 权限不必每次去查数据库,这样可以提高效率
- Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了
第3节 核心API
- Subject:主体,代表了当前 “用户”,获取用户传递过来的数据,然后传递给SecurityManager: 安全管理器
- SecurityManager: 安全管理器(Shiro的核心),将用户传递过来的认证信息和数据库中保存的信息进行校验
- Realm:域,Shiro的Realm主要从数据库中获取安全数据(如用户、角色、权限)通过方法传递给SecurityManager安全管理器进行数据验证
第4节 内部架构图
- Subject:当前用户主体(可以使任何与应用交互的用户)
- SecurityManager: 是Shiro的心脏;所有具体的交互都通过SecurityManager 进行控制;它管理着所有Subject、且负责进行认证和授权、及会话、缓存的管理
- Authenticator[ɔːˈθɛntɪkeɪtə]:认证器,负责主体认证的,这是一个扩展点,如果用户觉得 Shiro 默认的不好,可以自定义实现
- Authrizer:授权器,或者访问控制器
- Realm:可以有1个或多个Realm,是安全实体数据源,Shiro不知道你的用户 / 权限存储在哪及以何种格式存储,所以我们一般在应用中都需要实现自己的 Realm
- SessionManager: 会话管理器
- SessionDAO: session可以保存到数据库中或者是缓存中,或者是redis中,我们可以实现自己的SessionDAO对数据进行CRUD
- CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能
- Cryptography [krɪpˈtɒɡrəfi]:密码模块,Shiro提高了一些常见的加密组件用于如密码加密
第5节 核心名词介绍
1 2 3 4 | 身份验证就是我们通常说的登录,一般使用用户名/密码这样的常见组合,我们shiro中使用: 1. principals [ˈprɪnsəpəlz](用户名):身份,可以是任何东西,如用户名,邮箱等唯一即可。 2. credentials [krəˈdenʃlz]:证明 / 凭证,即只有主体知道的安全值,如密码 / 数字证书等 最常见的 principals 和 credentials 组合就是用户名 / 密码了 |
1 2 | Subject: 主体 Realm : 验证主体的数据源 |
第6节 Shiro核心对象介绍
- Md5Hash: MD5密码加密类
- DelegatingFilterProxy:代理类对象,存在spring-web包中,其作用就是一个filter的代理,用这个类的好处是可以通过spring容器来管理filter的生命周期
- ShiroFilterFactoryBean:ShiroFilter:权限控制的核心配置对象有Spring IOC容器创建,交给DelegatingFilterProxy代理
- DefaultWebSecurityManager: Shiro关于web的安全管理器对象
- AuthorizingRealm: 自定义Realm需要被继承的类,用于自定义Realm
- HashedCredentialsMatcher: 用于密码加密的类
- DefaultWebSessionManager: web的会话管理类
- UsernamePasswordToken: 封装用户名密码
- SimpleAuthenticationInfo:Realm认证方法的返回对象,封装从数据库查询出来认证的安全数据
- SimpleAuthorizationInfo:Realm授权方法的返回对象,封装从数据库查询出来授权的安全数据
1
以上是常见的认证/授权需要用到的类
第二章 Shiro快速入门
- Github源码下载
1
https://github.com/apache/shiro.git
- 在线源码下载
1
https://downloads.apache.org/shiro/1.2.6/shiro-root-1.2.6-source-release.zip
- 查看源码快速入门
-
- shiro.ini 文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
[users] # # 用户名=密码,角色 root: 用户名 secret:密码 admin: 角色 root = secret, admin # 用户名=密码,角色 guest = guest, guest # 用户名=密码,角色 presidentskroob = 12345, president # 用户名=密码,角色,角色 darkhelmet = ludicrousspeed, darklord, schwartz # 用户名=密码,角色,角色 lonestarr = vespa, goodguy, schwartz # ----------------------------------------------------------------------------- [roles] # 角色= * *: 通配符代表所有权限 admin = * # The 'schwartz' role can do anything (*) with any lightsaber: schwartz = lightsaber:* # The 'goodguy' role is allowed to 'drive' (action) the winnebago (type) with # license plate 'eagle5' (instance specific id) goodguy = winnebago:drive:eagle5
- shiro.ini 文件
-
- 入门代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
public static void main(String[] args) { // 创建安全管理器 SecurityManager Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini"); SecurityManager securityManager = factory.getInstance(); // 将安全管理器设置到SecurityUtils中 SecurityUtils.setSecurityManager(securityManager); // 获取实体 Subject currentUser = SecurityUtils.getSubject(); // 获取会话 Session session = currentUser.getSession(); session.setAttribute("someKey", "aValue"); String value = (String) session.getAttribute("someKey"); if (value.equals("aValue")) { log.info("Retrieved the correct value! [" + value + "]"); } // 判断是否认证 if (!currentUser.isAuthenticated()) { UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa"); token.setRememberMe(true); try { currentUser.login(token); } catch (UnknownAccountException uae) { log.info("There is no user with username of " + token.getPrincipal()); } catch (IncorrectCredentialsException ice) { log.info("Password for account " + token.getPrincipal() + " was incorrect!"); } catch (LockedAccountException lae) { log.info("The account for username " + token.getPrincipal() + " is locked. " + "Please contact your administrator to unlock it."); } // 其他异常 catch (AuthenticationException ae) { //unexpected condition? error? } } // log.info("User [" + currentUser.getPrincipal() + "] logged in successfully."); // 判断当前用户有哪些角色 if (currentUser.hasRole("schwartz")) { log.info("May the Schwartz be with you!"); } else { log.info("Hello, mere mortal."); } // 判断当前用户是否有lightsaber角色的weild权限 if (currentUser.isPermitted("lightsaber:weild")) { log.info("You may use a lightsaber ring. Use it wisely."); } else { log.info("Sorry, lightsaber rings are for schwartz masters only."); } // 判断当前用户是否有winnebago角色的drive权限的eagle5操作 if (currentUser.isPermitted("winnebago:drive:eagle5")) { log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'. " + "Here are the keys - have fun!"); } else { log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!"); } // 登出 currentUser.logout(); }
- 入门代码
第三章 加密技术
第1节 数据加密
所谓数据加密(Data Encryption)技术是指将一个信息(或称明文,plain text)经过加密钥匙(Encryption key)及加密函数转换,变成无意义的密文(cipher text),而接收方则将此密文经过解密函数、解密钥匙(Decryption key)还原成明文,加密技术是网络安全技术的基石
第2节 常见加密方式
2.1 对称加密
对称加密算法是应用较早的加密算法,又称为共享密钥加密算法。在对称加密算法中,使用的密钥只有一个,发送和接收双方都使用这个密钥对数据进行加密和解密.这就要求加密和解密方事先都必须知道加密的密钥.
2.2 非对称加密
非对称加密算法,又称为公开密钥加密算法。它需要两个密钥,一个称为公开密钥 (public key),即公钥,另一个称为私有密钥 (private key),即私钥。因为加密和解密使用的是两个不同的密钥,所以这种算法称为非对称加密算法.
第3节 常见的摘要算法
3.1 MD5加密
1 2 3 4 5 6 7 8 9 10 | MD5是一种摘要算法,它的典型应用是对一段信息产生信息摘要,以防止被篡改 public static final byte[] computeMD5(byte[] content) { try { MessageDigest md5 = MessageDigest.getInstance("MD5"); return md5.digest(content); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } } |
3.2 SHA-1加密
1 2 3 4 5 6 7 8 9 10 11 12 | SHA-1 是和 MD5 一样流行的 消息摘要算法,然而 SHA-1 比 MD5 的 安全性更强。对于长度小于 2 ^ 64 位的消息 SHA-1 会产生一个 160 位的 消息摘要。基于 MD5、SHA-1 的信息摘要特性以及 不可逆,可以被应用在检查文件完整性以及数字签名等场景 public static byte[] computeSHA1(byte[] content) { try { MessageDigest sha1 = MessageDigest.getInstance("SHA1"); return sha1.digest(content); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } } |
第四章 Shiro在ssm框架(spring+springmvc+mybatis)中的使用
第1节 ssm框架整合
- pom.xml依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
<properties> <spring.version>4.3.27.RELEASE</spring.version> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>javax</groupId> <artifactId>javaee-api</artifactId> <version>7.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-all</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>1.4.1</version> </dependency> <dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache-core</artifactId> <version>2.6.11</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-expression</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-instrument</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-instrument-tomcat</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jms</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-messaging</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-oxm</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc-portlet</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-websocket</artifactId> <version>${spring.version}</version> </dependency> <!-- mybatis核心包 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.5</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.1</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.40</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.8.7</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.8.7</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>2.8.7</version> </dependency> <!--打印日志 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.5</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.5</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <dependency> <groupId>jstl</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.6</version> </dependency> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.1.2</version> </dependency> </dependencies>
- 整合(略)
- 数据库SQL语句
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
-- 1.sys_users用户表 CREATE TABLE sys_users ( user_id bigint PRIMARY KEY AUTO_INCREMENT COMMENT '编号', username VARCHAR (100) UNIQUE COMMENT '用户名', password VARCHAR(100) COMMENT '密码', salt VARCHAR(100) COMMENT '盐值' ) charset=utf8 ENGINE=InnoDB COMMENT="用户表"; -- 2.sys_roles角色表 CREATE TABLE sys_roles ( role_id bigint PRIMARY KEY AUTO_INCREMENT COMMENT '角色编号', role_name VARCHAR(100) COMMENT '角色名称' ) charset=utf8 ENGINE=InnoDB COMMENT="角色表"; -- 3.sys_permissions权限表(或资源表) CREATE TABLE sys_permissions ( permission_id bigint PRIMARY KEY AUTO_INCREMENT COMMENT '编号', permission_name VARCHAR(100) COMMENT '权限' ) charset=utf8 ENGINE=InnoDB COMMENT="权限表"; -- 4.sys_users_roles用户-角色关联表 CREATE TABLE sys_users_roles ( ur_id bigint PRIMARY KEY AUTO_INCREMENT COMMENT '编号', user_id bigint COMMENT '用户编号', role_id bigint COMMENT '角色编号' ) charset=utf8 ENGINE=InnoDB COMMENT="用户-角色关联表"; -- 5.sys_roles_permissions角色-权限关联表(或角色-资源关联表) CREATE TABLE sys_roles_permissions ( rp_id bigint PRIMARY KEY AUTO_INCREMENT COMMENT '编号', role_id bigint COMMENT '角色编号', permission_id bigint COMMENT '权限编号' ) charset=utf8 ENGINE=InnoDB COMMENT="角色-权限关联表";
第2节 在WEB-INF/views文件夹下创建需要的页面
- 首页 index.jsp(首页创建在webapp下,非WEB-INF/views下)
1 2 3 4
<body> <h1>首页</h1> <a href="${pageContext.request.contextPath}/home">跳转Home页面</a> </body>
- 主页 home.jsp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
<body> <h1 style="text-align: center">Home页面</h1> <hr> <table border="1"> <thead> <tr> <th>测试请求地址</th> <th>操作</th> </tr> </thead> <tbody> <tr> <td> /jumpLogin </td> <td> <a href="${pageContext.request.contextPath}/jumpLogin">测试跳转登陆页面地址</a> </td> </tr> <tr> <td> /jumpRegister </td> <td> <a href="${pageContext.request.contextPath}/jumpRegister">测试跳转注册页面地址</a> </td> </tr> <tr> <td> /getUserList </td> <td> <a href="${pageContext.request.contextPath}/getUserList">测试获取用户列表页面地址</a> </td> </tr> <tr> <td> /jumpEdit </td> <td> <a href="${pageContext.request.contextPath}/jumpEdit">测试跳转更新页面地址</a> </td> </tr> </tbody> </table> </body>
- 登陆页面 login.jsp
1 2 3 4 5 6 7 8 9 10
<body> <h1>登录</h1> <form action="${pageContext.request.contextPath}/login" method="post"> <label for="username">用户名:</label> <input id="username" type="text" name="username"><br/> <label for="password">用户名:</label> <input id="password" type="text" name="password"><br/> <input type="submit" value="登录"> </form> </body>
- 注册页面 register.jsp
1 2 3 4 5 6 7 8 9 10
<body> <h1>注册</h1> <form action="${pageContext.request.contextPath}/register" method="post"> <label for="username">用户名:</label> <input id="username" type="text" name="username"><br/> <label for="password">用户名:</label> <input id="password" type="text" name="password"><br/> <input type="submit" value="注册"> </form> </body>
- 数据列表页面 user_list.jsp
1 2 3
<body> <h1>用户列表页面</h1> </body>
- 为授权页面 unauthorized.jsp
1 2 3
<body> <h1>没有权限</h1> </body>
- 更新页面 edit.jsp
1 2 3
<body> <h1 style="text-align: center">我是更新页面,我需要认证并且需要授权才能登陆</h1> </body>
第3节 Shiro的基本配置
- 在web.xml配置shiro的代理filter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
<filter> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <!-- 参数的意义: 默认targetFilterLifecycle为false,当他为false的时候shiroFilter代理对象默认加入到IOC容器中 并且在IOC容器中遵循IOC的生命周期管理,将其设置为true,让其受tomcat容器生命周期管理 --> <init-param> <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>
- 在spring的核心配置文件(applicatioContext.xml)中配置其他Shiro其他配置
-
- 配置被web.xml中Filter象代理的类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
<!--被web.xml中配置的Filter代理对象代理的类--> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <!--配置安全管理器--> <property name="securityManager" ref="securityManager"></property> <!--如果访问的页面或者是请求地址需要认证才能访问,在没有访问的时候访问了,跳转此地址--> <property name="loginUrl" value="/jumpLogin"></property> <!--设置没有授权需要被跳转的地址--> <property name="unauthorizedUrl" value="/jumpUnauthorized"></property> <!--设置拦截规则--> <property name="filterChainDefinitions"> <value> # anon: shiro的核心过滤器,表示/这个请求,可以匿名访问 / = anon /home = anon /jumpLogin = anon /jumpRegister = anon # authc: shiro的核心过滤器,表示getUserList必须认证(登陆)才能访问 /getUserList = authc # roles: shiro的核心过滤器,表示jumpEdit必须拥有admin角色才能访问 /jumpEdit = authc,roles[admin] # ** :通配符,表示除了上面配置的拦截,其余的所有请求都需要认证 /** = authc </value> </property> </bean>
- 配置被web.xml中Filter象代理的类
-
- Shiro常见核心过滤器介绍
-
- 配置Web安全管理器
1
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"></bean>
- 配置Web安全管理器
-
编写控制器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
@Controller public class UserController { /** * 跳转Home页面 */ @RequestMapping(value = "/home",method = RequestMethod.GET) public String home(){ System.out.println("跳转home.jsp页面,此请求地址[home]设置为匿名访问..."); return "home"; } /** * 跳转登陆页面 */ @RequestMapping(value = "/jumpLogin") public String jumpLogin(){ System.out.println("跳转login.jsp页面,此请求地址[jumpLogin]设置为匿名访问..."); return "login"; } /** * 跳转注册页面 */ @RequestMapping(value = "/jumpRegister") public String jumpRegister(){ System.out.println("跳转register.jsp页面,此请求地址[jumpRegister]设置为匿名访问..."); return "register"; } /** * 跳转用户列表页面 */ @RequestMapping(value = "/getUserList") public String getUserList(){ System.out.println("跳转user_list.jsp页面,此请求地址[getUserList]设置为认证访问..."); return "user_list"; } /** * 跳转更新页面 */ @RequestMapping(value = "/jumpEdit") public String jumpEdit(){ System.out.println("跳转edit.jsp页面,此请求地址[jumpEdit]设置为认证,并且判断此用户是否是访问这个请求的角色..."); return "edit"; } }
第4节 Shiro的其他配置
4.1 注册功能实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | /** * Shiro用户注册 * @param username: 用户名 * @param password: 密码 * @return */ @RequestMapping(value = "/register") public String register(String username, String password, Model model){ System.out.println("用户注册...入参为:"+username+"="+password); //判断前端发送过来的用户名和密码是否为空 if((username!=null && username.length()>0) && (password!=null && password.length()>0)){ //生成salt,我这里使用用户名作为盐,可以自己随意生成(比如UUDI或者随机数) String salt=username; /** * 在进行注册前要将密码进行盐值加密(我们采用MD5盐值加密方式),Shiro官方提供了Md5Hash类帮我们实现 * 我们这里使用3个参数的构造方法 * 第一个参数: 被加密的对象 * 第二个参数: 加的盐 * 第三个参数: 加密(迭代)次数 */ String source=password;//给谁加密 Md5Hash md5Hash = new Md5Hash(source,salt,1024); //获取进过加盐和循环迭代多次的密码 String targetPassword = md5Hash.toString(); //调用业务逻辑成,调用mapper层,将新用户信息保存到数据库中 SysUser sysUser = new SysUser(); sysUser.setUsername(username); sysUser.setPassword(targetPassword); sysUser.setSalt(salt); sysUserService.addSysUser(sysUser); //注册成功跳转到登录页 return "redirect:/jumpLogin"; }else { //注册失败 String msg="注册失败,用户名或者密码为空"; model.addAttribute("msg",msg); return "register"; } } |
4.2 登陆实现
4.2.1 控制器编写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | /** * 用户登录 * @param username: 用户名 * @param password: 密码 */ @RequestMapping(value = "/userLogin") public String userLogin(String username,String password){ System.out.println("用户登录...入参为:"+username+"="+password); /** * 用户登录采用Shiro帮助我们进行认证和授权 */ //获取Shiro实体 Subject subject = SecurityUtils.getSubject(); //使用Shiro提供的API对象封装前端传送过来的请求数据(用户名和密码) UsernamePasswordToken token = new UsernamePasswordToken(username, password); //调用Shiro提供的方法进行验证 try { subject.login(token); }catch (UnknownAccountException uae){ System.out.println("用户名不存在:"+uae.getMessage()); }catch (IncorrectCredentialsException ice){ System.out.println("密码错误:"+ice.getMessage()); }catch (LockedAccountException lae){ System.out.println("用户被锁定:"+lae.getMessage()); }catch (AuthenticationException ae){ <!--由自定义Realm抛出,在此捕获--> System.out.println("其他异常:"+ae.getMessage()); } //登陆成功跳转列表页(列表页需要认证才可以访问) return "redirect:/getUserList"; } |
4.2.2 自定义Realm实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | /** * realm:查询数据库获取用户认证和授权的数据,将其返回给安全管理器 */ public class SysUserRealm extends AuthorizingRealm { @Autowired private SysUserService sysUserService; @Autowired private SysUsersRolesService sysUsersRolesService; @Autowired private SysRoleService sysRoleService; @Autowired private SysRolesPermissionsService sysRolesPermissionsService; @Autowired private SysPermissionService sysPermissionService; /** * 授权操作 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("用户授权数据获取"+principalCollection); //获取用户名 String username = principalCollection.getPrimaryPrincipal().toString(); //通过用户名查询用户ID SysUser sysUser = sysUserService.getSysUserByUsername(username); //获取当前用户下的所有角色 List<SysUsersRoles> sysUsersRoles = sysUsersRolesService.getSysUsersRoles(sysUser.getUserId()); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); //将当前用户下的所有角色名称封装进SimpleAuthorizationInfo对象中 Set<String> roleNames = new HashSet<>(); for (SysUsersRoles usersRole : sysUsersRoles) { //通过角色ID查询角色名称 SysRole sysRole = sysRoleService.getSysRole(usersRole.getRoleId()); roleNames.add(sysRole.getRoleName()); //通过角色查询当前用户的操作权限名称 List<SysRolesPermissions> rolesPermissions = sysRolesPermissionsService.getSysRolesPermissionsList(usersRole.getRoleId()); Set<String> permissionList = new HashSet<>(); for (SysRolesPermissions rolesPermission : rolesPermissions) { //通过permissionId查询名称 SysPermission permission = sysPermissionService.getSysPermission(rolesPermission.getPermissionId()); permissionList.add(permission.getPermissionName()); } info.addStringPermissions(permissionList); } //将角色名称设置进SimpleAuthorizationInfo对象中 info.addRoles(roleNames); return info; } /** * 认证操作 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("用户认证数据获取"+authenticationToken); //获取用户名 String username = authenticationToken.getPrincipal().toString(); //使用用户名向数据库中查询数据 SysUser sysUser = sysUserService.getSysUserByUsername(username); //如果用户为空抛出异常在控制器层进行捕获 if(sysUser==null){ throw new AuthenticationException("用户名或密码错误"); } System.out.println("==========="+sysUser); //将查询出来的用户信息,通过SimpleAuthenticationInfo对象传递给安全管理器 //将数据库中查询出来的盐进行转换 ByteSource bytes = ByteSource.Util.bytes(sysUser.getSalt()); SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(sysUser.getUsername(),sysUser.getPassword(),bytes,getName()); return info; } } |
4.2.3 密码加密技术/以及自定义Realm配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <!--注册安全管理器--> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <!--配置自定义realm,从数据库获取数据--> <property name="realm" ref="sysUserRealm"></property> </bean> <!--配置自定义realm--> <bean id="sysUserRealm" class="com.qianfeng.shiro.SysUserRealm"> <!--设置密码加密方式(MD5盐值加密)--> <property name="credentialsMatcher" ref="credentialsMatcher"></property> </bean> <!--配置加密--> <bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"> <!--加密类型--> <property name="hashAlgorithmName" value="MD5"></property> <!--迭代次数--> <property name="hashIterations" value="1024"></property> </bean> |
4.3 多Realm实现
- 为什么要使用多Realm
1 2
如果当前应用多了很多外来用户(外来用户可能是公司兼并,或者是其他应用合并过来的等,总之认证数据不在同一个表里). 这时候一个Realm在实现起来比较不容易,可能两个表中,用户密码的加密方式不同,这时候一个Realm很难实现这样的策略,对于这种情况,Shiro提供了多Realm实现方式.
- 设置方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
Shiro中提供了一个多Realm的管理类ModularRealmAuthenticator帮助Shiro框架进行多个Realm管理 <!--注册安全管理器--> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <!--在安全管理器中设置多Realm管理器,重点: 多Realm策略,authenticator必须配置在realms上面,否则认证不通过--> <property name="authenticator" ref="authenticator"></property> <!--配置多Realm--> <property name="realms"> <list> <ref bean="userRealm01"></ref> <ref bean="userRealm02"></ref> </list> </property> </bean> <!--配置多Realm管理器--> <bean id="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator"> <!--设置多Realm管理器的策略--> <property name="authenticationStrategy"> <!--默认策略--> <bean class="org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy"></bean> </property> </bean> <!--配置Realm01--> <bean id="userRealm01" class="com.qianfeng.shiro.SysUserRealm"> <!--使用内部bean设置加密方式--> <property name="credentialsMatcher"> <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"> <property name="hashAlgorithmName" value="MD5"></property> <property name="hashIterations" value="1024"></property> </bean> </property> </bean> <!--配置Realm02--> <bean id="userRealm02" class="com.qianfeng.shiro.SysUserRealm"> <property name="credentialsMatcher"> <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"> <property name="hashAlgorithmName" value="SHA-1"></property> <property name="hashIterations" value="1024"></property> </bean> </property> </bean>
- 多Realm策略
-
- FirstSuccessfulStrategy
1
当有一个Realm认证成功就为成功,只返回第一个Realm身份验证 成功的认证信息,其他的忽略
- FirstSuccessfulStrategy
-
- AtLeastOneSuccessfulStrategy(默认)
1
只要有一个Realm验证成功即可,和FirstSuccessfulStrategy不同,返回所有Realm身份验证成功的认证信息
- AtLeastOneSuccessfulStrategy(默认)
-
- AllSuccessfulStrategy
1
所有Realm验证成功才算成功,且返回所有Realm身份验证成功的认证信息,如果有一个失败就失败
- AllSuccessfulStrategy
4.4 Shiro的缓存
- 开启缓存并配置缓存(在自定义Realm中定义)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
<!--配置自定义realm--> <bean id="sysUserRealm" class="com.qianfeng.shiro.SysUserRealm"> <!--设置密码加密方式(MD5盐值加密)--> <property name="credentialsMatcher" ref="credentialsMatcher"></property> <!--开启shiro缓存--> <property name="cachingEnabled" value="true"></property> <!--启用身份验证缓存,默认false--> <property name="authenticationCachingEnabled" value="true"></property> <!--启用授权缓存,默认false--> <property name="authorizationCachingEnabled" value="true"></property> <!--缓存 AuthenticationInfo 信息的缓存名称--> <property name="authenticationCacheName" value="authenticationCache"></property> <!--缓存 AuthorizationInfo 信息的缓存名称--> <property name="authorizationCacheName" value="authorizationCache"></property> <!--配置ehcache缓存--> <property name="cacheManager"> <bean class="org.apache.shiro.cache.ehcache.EhCacheManager"></bean> </property> </bean>
4.4 常见的JSP标签
1 2
Shiro为JSP页面提供了标签库类似于我们的JSTL,标签库地址为: <%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
- guest 标签
1 2 3 4 5
<shiro:guest> 欢迎游客访问,<a href="${pageContext.request.contextPath}/login.jsp">登录</a> </shiro:guest> 用户没有身份验证时显示相应信息,即游客访问信息
- authenticated 标签
1 2 3 4 5
<shiro:authenticated> 用户[<shiro:principal/>]已身份验证通过 </shiro:authenticated> 用户已经身份验证通过,即 Subject.login 登录成功
- principal 标签
1 2 3
<shiro:principal/> 显示用户身份信息
- hasRole 标签
1 2 3 4 5
<shiro:hasRole name="admin"> 用户[<shiro:principal/>]拥有角色admin </shiro:hasRole> 如果当前 Subject 有角色admin,显示内部内容
- hasAnyRoles 标签
1 2 3 4 5
<shiro:hasAnyRoles name="admin,user"> 用户[<shiro:principal/>]拥有角色admin或user<br/> </shiro:hasAnyRoles> 如果当前 Subject 有任意一个角色(或的关系)将显示内部内容
- hasPermission 标签
1 2 3
<shiro:hasPermission name="user:create"> 用户[<shiro:principal/>]拥有权限user:create<br/> </shiro:hasPermission>
4.5 Shiro的会话管理
1 2 3
1、所谓会话,即用户访问应用时保持的连接关系,会话不结束,服务器可以一直识别当前用户 2、如果关闭浏览器,此次会话结束,下次在访问服务器,服务器会认为是全新的一次访问.需要重新认证 3、如果长时间保持连接,但是用户没有任何操作,那么会出现会话超时的问题,默认tomcat为30分钟
- Shiro多采用DefaultWebSessionManager进行会话管理
1 2 3 4 5 6 7 8 9 10 11 12 13
<!--会话管理--> <bean id="webSessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager"> <!--会话超时时间,单位毫秒--> <property name="globalSessionTimeout" value="10000"></property> </bean> <!--配置完会话之后将其设置到安全管理器中--> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <!--配置自定义realm,从数据库获取数据--> <property name="realm" ref="sysUserRealm"></property> <!--会话管理--> <property name="sessionManager" ref="webSessionManager"></property> </bean>
- 重启服务登陆,查看会话过期时间是否生效
4.6 记住我功能实现
- 新增记住我测试页面 rememberme.jsp
1 2 3
<body> <h1>记住我页面测试</h1> </body>
- 在控制器中添加方法(测试记住我)
1 2 3 4 5 6 7 8 9
/** * 记住我页面测试 */ @RequestMapping(value = "/testRememberMe") public String testRememberMe(){ System.out.println("测试记住我..."); //跳转记住我页面 return "rememberme"; }
- 在配置文件中配置记住我功能(在安全管理器中)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
<!--注册安全管理器--> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <!--配置自定义realm,从数据库获取数据--> <property name="realm" ref="sysUserRealm"></property> <!--会话管理--> <property name="sessionManager" ref="webSessionManager"></property> <!--记住我--> <property name="rememberMeManager"> <bean class="org.apache.shiro.web.mgt.CookieRememberMeManager"> <property name="cookie"> <bean class="org.apache.shiro.web.servlet.SimpleCookie"> <!--自定义cookie名称--> <constructor-arg value="qianfeng"></constructor-arg> <!--防止前端js使用document.cookie获取cookie--> <property name="httpOnly" value="true"></property> <!--过期时间,默认在浏览器关闭是过期 -1 有效期30天--> <property name="maxAge" value="2592000"></property> </bean> </property> </bean> </property> </bean>
- 修改ShiroFilterFactoryBean定义的规则(添加记住我拦截规则)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <!--配置安全管理器--> <property name="securityManager" ref="securityManager"></property> <!--如果访问的页面或者是请求地址需要认证才能访问,在没有访问的时候访问了,跳转此地址--> <property name="loginUrl" value="/jumpLogin"></property> <!--设置没有授权需要被跳转的地址--> <property name="unauthorizedUrl" value="/jumpUnauthorized"></property> <!--设置拦截规则--> <property name="filterChainDefinitions"> <value> # anon: shiro的核心过滤器,表示/这个请求,可以匿名访问 / = anon /home = anon /jumpLogin = anon /jumpRegister = anon /register = anon /userLogin = anon # authc: shiro的核心过滤器,表示getUserList必须认证(登陆)才能访问 /getUserList = authc # roles: shiro的核心过滤器,表示jumpEdit必须拥有admin角色才能访问 /jumpEdit = authc,roles[admin] # ** :通配符,表示除了上面配置的拦截,其余的所有请求都需要认证 #/** = authc #测试记住我,将通配拦截取消 # user:shiro的核心过滤器,表示地址可以使用记住我登陆 /testRememberMe = user </value> </property> </bean>
- 控制器中修改登陆的规则(在登陆中调用Shiro记住我API方法)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
/** * 用户登录 * @param username: 用户名 * @param password: 密码 */ @RequestMapping(value = "/userLogin") public String userLogin(String username,String password){ System.out.println("用户登录...入参为:"+username+"="+password); /** * 用户登录采用Shiro帮助我们进行认证和授权 */ //获取Shiro实体 Subject subject = SecurityUtils.getSubject(); //使用Shiro提供的API对象封装前端传送过来的请求数据(用户名和密码) UsernamePasswordToken token = new UsernamePasswordToken(username, password); token.setRememberMe(true);//记住我 //调用Shiro提供的方法进行验证 try { subject.login(token); }catch (UnknownAccountException uae){ System.out.println("用户名不存在:"+uae.getMessage()); }catch (IncorrectCredentialsException ice){ System.out.println("密码错误:"+ice.getMessage()); }catch (LockedAccountException lae){ System.out.println("用户被锁定:"+lae.getMessage()); }catch (AuthenticationException ae){ System.out.println("其他异常:"+ae.getMessage()); } //登陆成功跳转列表页(列表页需要认证才可以访问) return "redirect:/getUserList"; }
- 测试(打开浏览器调试模式)