学习一门新技术的思路:html
是什么?vue
是用来干吗的,应用在哪些地方?java
为何要学?mysql
与它相关的有哪些,须要另外了解的知识?jquery
怎样去学?ios
官方文档:git
http://shiro.apache.org/index.htmlgithub
官方介绍:web
Apache Shiro™ is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management. With Shiro’s easy-to-understand API, you can quickly and easily secure any application – from the smallest mobile applications to the largest web and enterprise applications.面试
Primary Concerns:
Supporting Features:
......
但,Shiro不会去维护用户、维护权限,这些须要咱们本身去设计/提供,而后经过相应的接口注入给Shiro
其中Authentication(认证), Authorization(受权), Session Management(会话管理), Cryptography(加密)被 Shiro 框架的开发团队称之为应用安全的四大基石
优势(为何要学?):
易于使用、全面、灵活、Web支持、低耦合、被普遍支持普遍使用(是Apache软件基金会的一部分)
公司要用、面试要问。。。
维基百科:以角色为基础的访问控制(Role-based access control,RBAC),是资讯安全领域中,一种较新且广为使用的访问控制机制,不直接赋予使用者权限,而是将权限赋予角色。
RBAC经过角色关联用户,角色关联权限的方式间接赋予用户权限。以下图
有人会问为何不直接给用户分配权限,还画蛇添足的增长角色这一环节呢?
实际上是能够直接给用户分配权限,只是直接给用户分配权限,少了一层关系,扩展性弱了许多,适合那些用户数量、角色类型少的平台。
对于一般的系统,好比:存在多个用户拥有相同的权限,在分配的时候就要分别为这几个用户指定相同的权限,修改时也要为这几个用户的权限进行一一修改。有了角色后,咱们只须要为该角色制定好权限后,将相同权限的用户都指定为同一个角色便可,便于权限管理。
对于批量的用户权限调整,只需调整用户关联的角色权限,无需对每个用户都进行权限调整,既大幅提高权限调整的效率,又下降了漏调权限的几率。
小结:
RBAC 的优势主要在于易用和高效。给用户受权时只须要对角色受权,而后将相应的角色分配给用户便可;从技术角度讲,思路清晰且易于实现,且后期维护时只须要维护关系模型,显得简单而高效。
RBAC 的缺点主要有两个:一个是在进行较为复杂的权限校验时须要不断地遍历和递归,会形成必定的性能影响。另外一个是缺乏数据权限模型,基于 RBAC 来实现数据权限校验比较复杂和低效。
如今主流的权限管理系统设计大多仍是基于RBAC模型的,只是根据不一样的业务和设计方案,呈现不一样的显示效果。
RBAC模型能够分为:RBAC0、RBAC一、RBAC二、RBAC3 四种。其中RBAC0是基础,也是最简单的,至关于底层逻辑,RBAC一、RBAC二、RBAC3都是以RBAC0为基础的升级。
通常状况下,使用RBAC0模型就能够知足常规的权限管理系统设计了。
RBAC0模型:
最简单的用户、角色、权限模型。是基础,定义了能构成 RBAC 权限控制系统的最小的集合。
RBAC0 由四部分构成:
RBAC0对应的表结构:
RBAC0 里面又包含了2种(用户和角色的表关系):
那么,何时该使用多对一的权限体系,何时又该使用多对多的权限体系呢?
若是系统功能比较单一,使用人员较少,岗位权限相对清晰且确保不会出现兼岗的状况,此时能够考虑用多对一的权限体系。其他状况尽可能使用多对多的权限体系,保证系统的可扩展性。如:张三既是行政,也负责财务工做,那张三就同时拥有行政和财务两个角色的权限。
角色与权限是多对多关系,用户与权限之间也是多对多关系,经过角色间接创建。
3张基础表:用户、角色、权限
2张中间表:创建用户与角色的多对多关系,角色与权限的多对多关系。
DROP DATABASE IF EXISTS shiro; CREATE DATABASE shiro DEFAULT CHARACTER SET utf8; USE shiro; DROP TABLE IF EXISTS `user`; DROP TABLE IF EXISTS role; DROP TABLE IF EXISTS permission; DROP TABLE IF EXISTS user_role; DROP TABLE IF EXISTS role_permission; /*用户表*/ CREATE TABLE `user` ( id BIGINT AUTO_INCREMENT, NAME VARCHAR(100), PASSWORD VARCHAR(100), CONSTRAINT pk_users PRIMARY KEY(id) ) CHARSET=utf8 ENGINE=INNODB; /*角色表*/ CREATE TABLE role ( id BIGINT AUTO_INCREMENT, NAME VARCHAR(100), CONSTRAINT pk_roles PRIMARY KEY(id) ) CHARSET=utf8 ENGINE=INNODB; /*权限表*/ CREATE TABLE permission ( id BIGINT AUTO_INCREMENT, NAME VARCHAR(100), CONSTRAINT pk_permissions PRIMARY KEY(id) ) CHARSET=utf8 ENGINE=INNODB; /*用户角色(关系)表*/ CREATE TABLE user_role ( uid BIGINT, rid BIGINT, CONSTRAINT pk_users_roles PRIMARY KEY(uid, rid) ) CHARSET=utf8 ENGINE=INNODB; /*角色权限(关系)表*/ CREATE TABLE role_permission ( rid BIGINT, pid BIGINT, CONSTRAINT pk_roles_permissions PRIMARY KEY(rid, pid) ) CHARSET=utf8 ENGINE=INNODB;
其余模型这里不作过多深究介绍,其余模型的理解参考此篇文章:
http://www.woshipm.com/pd/1150093.html
权限是资源的集合。
这里的资源指的是软件中全部的内容,包括模块、菜单、页面、字段、操做功能(增删改查)等等。
具体的权限配置上,能够将权限分为:页面权限、操做权限和数据权限
页面权限:全部系统都是由一个个的页面组成,页面再组成模块,用户是否能看到这个页面的菜单、是否能进入这个页面就称为页面权限。
操做权限:用户凡是在操做系统中的任何动做、交互都是操做权限,如增删改查等。
数据权限:通常业务管理系统,都有数据私密性的要求:哪些人能够看到哪些数据,不能够看到哪些数据。
与Shiro相关的,可能就是SpringSecurity了
官网:https://spring.io/projects/spring-security
Spring Security is a powerful and highly customizable authentication and access-control framework. It is the de-facto standard for securing Spring-based applications.
Spring Security is a framework that focuses on providing both authentication and authorization to Java applications. Like all Spring projects, the real power of Spring Security is found in how easily it can be extended to meet custom requirements
是一个认证和访问控制(受权)框架,来保护基于Spring的应用程序,专一于为java应用提供认证和受权。
SpringSecurity属于Spring全家桶的一部分,对于Spring项目来讲,其实使用它是讨巧的。
关于Shiro和SpringSecurity的对比,笔者在网上查询了下资料,并没发现有讲得很好的。
大多说的是因需使用,看使用场景,选择使用。但在简单性上,仍是优先选择Shiro。本身对这两大框架的应用也并无太多,所以也没法讲清。在此只是提一嘴SpringSecurity,感兴趣的可自行对比研究。
根据官网:
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.4.1</version> </dependency> <!-- Shiro uses SLF4J for logging. We'll use the 'simple' binding in this example app. See http://www.slf4j.org for more info. --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> <version>1.7.21</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> <version>1.7.21</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
https://github.com/apache/shiro/blob/master/samples/quickstart/src/main/resources/log4j.properties
log4j.properties:
log4j.rootLogger=INFO, stdout log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n # General Apache libraries log4j.logger.org.apache=WARN # Spring log4j.logger.org.springframework=WARN # Default Shiro logging log4j.logger.org.apache.shiro=INFO # Disable verbose logging log4j.logger.org.apache.shiro.util.ThreadContext=WARN log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN
shiro.ini:
[users] # user 'root' with password 'secret' and the 'admin' role root = secret, admin # user 'guest' with the password 'guest' and the 'guest' role guest = guest, guest # user 'presidentskroob' with password '12345' ("That's the same combination on # my luggage!!!" ;)), and role 'president' presidentskroob = 12345, president # user 'darkhelmet' with password 'ludicrousspeed' and roles 'darklord' and 'schwartz' darkhelmet = ludicrousspeed, darklord, schwartz # user 'lonestarr' with password 'vespa' and roles 'goodguy' and 'schwartz' lonestarr = vespa, goodguy, schwartz # ----------------------------------------------------------------------------- # Roles with assigned permissions # # Each line conforms to the format defined in the # org.apache.shiro.realm.text.TextConfigurationRealm#setRoleDefinitions JavaDoc # ----------------------------------------------------------------------------- [roles] # 'admin' role has all permissions, indicated by the wildcard '*' 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
官网上有一个10分钟教程,它让咱们先看Quickstart.java学习
https://github.com/apache/shiro/blob/master/samples/quickstart/src/main/java/Quickstart.java
import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.*; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.session.Session; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Simple Quickstart application showing how to use Shiro's API. * 简单的快速启动应用程序,演示如何使用Shiro的API。 * @since 0.9 RC2 */ public class Quickstart { //日志门面,默认是commons-logging private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class); public static void main(String[] args) { Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini"); SecurityUtils.setSecurityManager(securityManager); // Now that a simple Shiro environment is set up, let's see what you can do: // get the currently executing user: //得到当前执行用户(重要!!) Subject currentUser = SecurityUtils.getSubject(); // Do some stuff with a Session (no need for a web or EJB container!!!) //不一样于HttpSession,不须要Web或EJB的容器支持 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 + "]"); } // let's login the current user so we can check against roles and permissions: //当前用户身份验证 if (!currentUser.isAuthenticated()) { //建立标记,其中用户名和密码是读取shiro.ini配置文件中的 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 more exceptions here (maybe custom ones specific to your application? catch (AuthenticationException ae) {//大异常,相似java中的Exception //unexpected condition? error? } } //say who they are: //print their identifying principal (in this case, a username): log.info("User [" + currentUser.getPrincipal() + "] logged in successfully."); //test a role: if (currentUser.hasRole("schwartz")) { log.info("May the Schwartz be with you!"); } else { log.info("Hello, mere mortal."); } //粗粒度 //test a typed permission (not instance-level) if (currentUser.isPermitted("lightsaber:wield")) { log.info("You may use a lightsaber ring. Use it wisely."); } else { log.info("Sorry, lightsaber rings are for schwartz masters only."); } //细粒度 //a (very powerful) Instance Level permission: 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!"); } //注销 //all done - log out! currentUser.logout(); //结束系统 System.exit(0); } }
运行结果:
打印了一堆默认的日志消息
2020-09-19 13:09:07,652 INFO [org.apache.shiro.session.mgt.AbstractValidatingSessionManager] - Enabling session validation scheduler... 2020-09-19 13:09:07,733 INFO [Quickstart] - Retrieved the correct value! [aValue] 2020-09-19 13:09:07,734 INFO [Quickstart] - User [lonestarr] logged in successfully. 2020-09-19 13:09:07,734 INFO [Quickstart] - May the Schwartz be with you! 2020-09-19 13:09:07,734 INFO [Quickstart] - You may use a lightsaber ring. Use it wisely. 2020-09-19 13:09:07,735 INFO [Quickstart] - You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'. Here are the keys - have fun!
<!-- https://mvnrepository.com/artifact/commons-logging/commons-logging --> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency>
<scope>runtime</scope>
做用域①类的描述
/** * Simple Quickstart application showing how to use Shiro's API. * 简单的快速启动应用程序,演示如何使用Shiro的API。 */
②经过工厂模式建立SecurityManager的实例对象
// 读取类路径下的shiro.ini文件 // Use the shiro.ini file at the root of the classpath // (file: and url: prefixes load from files and urls respectively): Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini"); SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); // 已经创建了一个简单的Shiro环境
③获取当前的Subject
// get the currently executing user: 获取当前正在执行的用户 Subject currentUser = SecurityUtils.getSubject();
④session的操做
// 用会话作一些事情(不须要web或EJB容器!!!) // Do some stuff with a Session (no need for a web or EJB container!!!) //存值取值 Session session = currentUser.getSession(); //得到session session.setAttribute("someKey", "aValue"); //设置Session的值! String value = (String) session.getAttribute("someKey"); //从session中获取 值 if (value.equals("aValue")) { //判断session中是否存在这个值! log.info("==Retrieved the correct value! [" + value + "]"); }
⑤用户认证
// 测试当前的用户是否已经被认证,便是否已经登陆! // let's login the current user so we can check against roles and permissions: if (!currentUser.isAuthenticated()) { // isAuthenticated();是否定证 //将用户名和密码封装为 UsernamePasswordToken ; UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa"); token.setRememberMe(true); //记住我功能 try { currentUser.login(token); //执行登陆,能够登陆成功的! } catch (UnknownAccountException uae) { //若是没有指定的用户,则UnknownAccountException异常 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 more exceptions here (maybe custom ones specific toyour application? catch (AuthenticationException ae) { //认证异常,上面的异常都是它的子类 //unexpected condition? error? } } // log.info("User [" + currentUser.getPrincipal() + "] logged insuccessfully.");
⑥角色检查
//test a role: //是否存在某一个角色 if (currentUser.hasRole("schwartz")) { log.info("May the Schwartz be with you!"); } else { log.info("Hello, mere mortal."); }
⑦权限检查,粗粒度
//测试用户是否具备某一个权限,行为 //test a typed permission (not instance-level) if (currentUser.isPermitted("lightsaber:wield")) { log.info("You may use a lightsaber ring. Use it wisely."); } else { log.info("Sorry, lightsaber rings are for schwartz masters only."); }
⑧权限检查,细粒度
//测试用户是否具备某一个权限,行为,比上面更加的具体! //a (very powerful) Instance Level permission: 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!"); }
⑨注销操做
//执行注销操做! //all done - log out! currentUser.logout();
⑩退出系统
System.exit(0);
Shiro 架构包含三个主要的理念:Subject、SecurityManager和 Realm
咱们须要实现Realms的Authentication 和 Authorization。其中 Authentication 是用来验证用户身份,Authorization 是受权访问控制,用于对用户进行的操做受权,证实该用户是否容许进行当前操做,如访问某个连接,某个资源文件等。
内部架构
Shiro中其余的一些概念:
场景任务:
一、建库建表sql
DROP DATABASE IF EXISTS shiro; CREATE DATABASE shiro DEFAULT CHARACTER SET utf8; USE shiro; /*用户表*/ /*salt 是盐,用来和 shiro 结合的时候,加密用的*/ CREATE TABLE `user`( id INT(11) NOT NULL AUTO_INCREMENT, `name` VARCHAR(255)DEFAULT NULL, `password` VARCHAR(255)DEFAULT NULL, salt VARCHAR(255)DEFAULT NULL, PRIMARY KEY(id) )ENGINE=INNODB DEFAULT CHARSET=utf8;
二、新建一个SpringBoot项目
勾选lombok、SpringWeb、thymeleaf、JDBC API以及MySQL Driver
三、导入Pom依赖,shiro两种方式
第一种:
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>1.4.0</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.0</version> </dependency>
第二种:
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring-boot-web-starter --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-web-starter</artifactId> <version>1.6.0</version> </dependency>
其中第二种会报错,在末尾第九部分会具体说明出现了什么错误以及怎么解决
mybatis依赖:
<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.1</version> </dependency>
四、配置application.yml文件
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/shiro?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8 username: root password: admin thymeleaf: cache: false encoding: utf-8 mode: HTML5 servlet: content-type: text/html mybatis: mapper-locations: classpath:/mapper/*.xml type-aliases-package: com.cqy.shiro.pojo configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl server: servlet: context-path: /shiro
五、编写一个简单的register.html注册页面(使用Vue)
<!DOCTYPE html> <html lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>注册</title> <script src="js/vue/2.5.16/vue.min.js"></script> <script src="js/axios/0.17.1/axios.min.js"></script> <!-- <script src="js/jquery/2.0.0/jquery.min.js"></script>--> <script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script> </head> <body> <script> $(function () { var data4 = { uri: 'submitregister', user: { name: '', password:'' } }; var vue = new Vue({ el: '#work', data: data4, mounted: function(){ }, methods: { submit: function () { var url = this.uri; axios.post(url,this.user).then(function (response) { //暂时不写,后面写了controller后再编写 }); } } }); }); </script> <div id="work"> <h3>注册</h3> 用户名:<input type="text" v-model="user.name"><br> 密码:<input type="password" v-model="user.password"><br> <button type="button" @click="submit">提交</button> </div> </body> </html>
六、PageController用于页面跳转
register.html放在templates里面,直接访问不了,须要内部跳转访问
@Controller public class PageController { @GetMapping("/register") public String register(){ return "register"; } }
七、建立pojo,User类
@Data @AllArgsConstructor @NoArgsConstructor public class User { private int id; private String name; private String password; private String salt; }
八、建立UserMapper和UserMapper.xml
这里为了便捷,直接使用Class再也不进行dao、service层的编写
提供两个方法
@Repository public interface UserMapper { //根据name查询用户 public User queryUserByName(String name); //添加用户 public int addUser(User user); }
<?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.cqy.shiro.mapper.UserMapper"> <select id="queryUserByName" resultType="user"> select * from `user` where `name`=#{name} </select> <insert id="addUser" parameterType="user"> insert into `user` values (#{id},#{name},#{password},#{salt}) </insert> </mapper>
九、建立一个login.html登陆页面用于登陆
<!DOCTYPE html> <html lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>登陆</title> <script src="js/vue/2.5.16/vue.min.js"></script> <script src="js/axios/0.17.1/axios.min.js"></script> <!-- <script src="js/jquery/2.0.0/jquery.min.js"></script>--> <script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script> </head> <body> <script> $(function () { var data4 = { uri: 'submitlogin', user: { name: '', password:'' } }; var vue = new Vue({ el: '#work', data: data4, methods: { submit: function () { var url = this.uri; axios.post(url,this.user).then(function (response) { //暂时不写,后面写了controller后再编写 }); } } }); }); </script> <div id="work"> <h3>登陆</h3> 用户名:<input type="text" v-model="user.name"><br> 密码:<input type="password" v-model="user.password"><br> <button type="button" @click="submit">登陆</button> </div> </body> </html>
一、导入Shiro的Pom依赖(文上)
二、自定义一个 Realm 的类
用来编写一些认证与受权的逻辑
//自定义Realm public class UserRealm extends AuthorizingRealm { //执行受权逻辑 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { //执行时机:在用户进行登陆,认证身份时执行 System.out.println("====>>>>执行了受权逻辑PrincipalCollection"); return null; } //执行认证逻辑 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { //执行时机:在用户登陆后,每访问一次那些须要权限才能访问的页面时执行 System.out.println("====>>>>执行了认证逻辑AuthenticationToken"); //暂时为空,后面根据业务逻辑编写认证代码 return null; } }
三、编写Shiro配置类,config包下
@Configuration public class ShiroConfig { //建立ShiroFilterFactoryBean @Bean public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){ ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); //设置安全管理器 shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager); return shiroFilterFactoryBean; } //建立DefaultWebSecurityManager @Bean public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("getUserRealm") UserRealm userRealm){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); //关联Realm securityManager.setRealm(userRealm); return securityManager; } //建立Hash凭证匹配器 @Bean public HashedCredentialsMatcher hashedCredentialsMatcher() { HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); //md5默认就是加密1次 hashedCredentialsMatcher.setHashAlgorithmName("md5"); hashedCredentialsMatcher.setHashIterations(1); return hashedCredentialsMatcher; } //建立Realm对象,须要自定义 @Bean public UserRealm getUserRealm(@Qualifier("hashedCredentialsMatcher") HashedCredentialsMatcher hashedCredentialsMatcher){ UserRealm userRealm = new UserRealm(); //关联凭证匹配器!!! userRealm.setCredentialsMatcher(hashedCredentialsMatcher); return userRealm; } }
一、编写注册的Controller
@RestController public class ShiroController { @Autowired UserMapper userMapper; @PostMapping("/submitregister") public Object submitRegister(@RequestBody User user){ String name = user.getName(); String password = user.getPassword(); //根据name从数据库中查询对应的user if (null!=userMapper.queryUserByName(name)){ return "用户名已经被使用,请从新输入"; } //经过随机方式建立盐,而且加密算法采用md5 String salt = new SecureRandomNumberGenerator().nextBytes().toString(); //加密次数,默认加密1次 int times = 1; String algorithmName = "md5"; String encodePassword = new SimpleHash(algorithmName,password,salt,times).toString(); //盐若是丢失了,没法验证密码是否正确,所以会添加到数据库保存 user.setSalt(salt); user.setPassword(encodePassword); //添加到数据库 userMapper.addUser(user); return 0; } }
二、register.html修改
<!DOCTYPE html> <html lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>注册</title> <script src="js/vue/2.5.16/vue.min.js"></script> <script src="js/axios/0.17.1/axios.min.js"></script> <!-- <script src="js/jquery/2.0.0/jquery.min.js"></script>--> <script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script> </head> <body> <script> $(function () { var data4 = { uri: 'register', user: { name: '', password:'' } }; var vue = new Vue({ el: '#work', data: data4, mounted: function(){ }, methods: { submit: function () { var url = this.uri; axios.post(url,this.user).then(function (response) { var result = response.data; //若是注册成功,跳转到注册成功页面 if (0===result){ location.href="registerSuccess"; } }); } } }); }); </script> <div id="work"> <h3>注册</h3> 用户名:<input type="text" v-model="user.name"><br> 密码:<input type="password" v-model="user.password"><br> <button type="button" @click="submit">提交</button> </div> </body> </html>
三、建立registerSuccess.html注册成功页面
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>注册成功页面</title> </head> <body> <h1>恭喜您,注册成功!</h1> </body> </html>
四、PageController中添加跳转
@GetMapping("/registerSuccess") public String registerSuccess(){ return "registerSuccess"; }
五、启动SpringBoot项目
访问http://localhost:8080/shiro/register
输入用户名和密码,点击提交进行注册
发现页面跳转到注册成功页面,查看数据库user表
加密注册成功!
一、编写Realm里的认证逻辑
@Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { //将AuthenticationToken转成UsernamePasswordToken UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; //获取帐号名 String username = token.getUsername(); //根据name获取用户对象从而拿到密码和盐值 User user = userMapper.queryUserByName(username); //获取用户密码(数据库中加密后的),须要经过user对象来拿 String passwordInDB = user.getPassword(); //拿到盐 String salt = user.getSalt(); //认证信息里存放帐号密码,getName()是当前Realm的继承方法,一般返回当前类名:UserRealm SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(username, passwordInDB, ByteSource.Util.bytes(salt), getName()); return simpleAuthenticationInfo; }
二、编写登陆的Controller,在ShiroController中
@PostMapping("/submitlogin") public Object submitLogin(@RequestBody User userParam){ String name = userParam.getName(); String password = userParam.getPassword(); //使用Shiro方式获取当前用户 Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken(name, password); //执行登陆,与Realm中查询的数据库中用户的信息进行比较 try { subject.login(token); //根据用户名 User user = userMapper.queryUserByName(name); subject.getSession().setAttribute("user",user); return 0; } catch (AuthenticationException e) { return "帐号或密码错误"; } }
三、login.html修改
<!DOCTYPE html> <html lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>登陆</title> <script src="js/vue/2.5.16/vue.min.js"></script> <script src="js/axios/0.17.1/axios.min.js"></script> <!-- <script src="js/jquery/2.0.0/jquery.min.js"></script>--> <script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script> </head> <body> <script> $(function () { var data4 = { uri: 'submitlogin', user: { name: '', password:'' } }; var vue = new Vue({ el: '#work', data: data4, methods: { submit: function () { var url = this.uri; axios.post(url,this.user).then(function (response) { var result = response.data; //若是返回0,说明认证成功,即跳转到登录成功页面 if (0===result){ location.href="loginSuccess"; } }); } } }); }); </script> <div id="work"> <h3>登陆</h3> 用户名:<input type="text" v-model="user.name"><br> 密码:<input type="password" v-model="user.password"><br> <button type="button" @click="submit">登陆</button> </div> </body> </html>
四、建立loginSuccess.html登陆成功页面
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>登陆成功页面</title> </head> <body> <h1>恭喜您登陆成功!</h1> <span>当前用户是: <a th:text="${session.user.name}"></a> </span> </body> </html>
五、PageController中添加跳转
@GetMapping("/loginSuccess") public String loginSuccess(){ return "loginSuccess"; }
六、启动SpringBoot项目
访问http://localhost:8080/shiro/login
输入用户名密码,点击登陆
发现页面跳转到登陆成功页面,并显示出了当前登陆用户:纸飞机
一、在templates目录下新建一个user目录,编写两个页面add.html、update.html
<body> <h1>add</h1> </body>
<body> <h1>update</h1> </body>
二、编写对应跳转到两个页面的Controller
@GetMapping("/user/add") public String toAdd(){ return "user/add"; } @GetMapping("/user/update") public String toUpdate(){ return "user/update"; }
三、在templates目录下建立一个首页index.html
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>首页</title> </head> <body> <a href="login">点击进入登陆页面</a> <br> <br> <a th:href="@{/user/add}">add</a> | <a th:href="@{/user/update}">update</a> </body> </html>
四、跳转到首页index.html的Controller
@GetMapping({"/","/index"}) public String toIndex(){ return "index"; }
五、运行测试页面跳转是否OK(能够直接进入add或update页面)
六、准备编写Shiro的内置过滤器(设置过滤规则)
//建立ShiroFilterFactoryBean @Bean(name ="shiroFilterFactoryBean") public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){ ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); //设置安全管理器 shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager); //修改到要跳转的login页面; shiroFilterFactoryBean.setLoginUrl("/login"); /* * 经常使用的有以下过滤器: * anon: 无需认证就能够访问 * authc: 必须认证才能够访问 * user: 若是使用了记住我功能就能够直接访问 * perms: 拥有某个资源权限才能够访问 * role: 拥有某个角色权限才能够访问 */ //注意此处使用的是LinkedHashMap,是有顺序的,shiro会按从上到下的顺序匹配验证,匹配了就再也不继续验证 Map<String, String> filterMap = new LinkedHashMap<>(); filterMap.put("/user/add","authc"); filterMap.put("/user/update","authc"); //能够直接使通配符 //filterMap.put("/user/*","authc"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap); return shiroFilterFactoryBean; }
七、启动测试访问,发现点击add|update连接没法进入,已经被拦截,会自动给咱们跳转到login.jsp页面。咱们须要自定义跳转页面。
八、在ShiroFilterFactoryBean方法中添加shiroFilterFactoryBean.setLoginUrl
//修改到要跳转的login页面; //前面咱们已经建立好了login.html页面和跳转的Controller shiroFilterFactoryBean.setLoginUrl("/login");
九、再次启动测试,发现点击add|update连接会直接跳转到login.html登陆页面让你进行登陆
一、在登陆成功页面loginSuccess添加退出的连接
<a href="logout">退出</a>
二、编写退出的Controller
//退出登陆,跳转到首页 @GetMapping("/logout") public String loginOut() { Subject subject = SecurityUtils.getSubject(); if(subject.isAuthenticated()) subject.logout();//会自动让session失效 return "redirect:index"; }
简单Shiro的过滤器拦截请求
一、在登陆成功页面loginSuccess添加跳转到首页index.html的连接
<a href="index">点击跳转到首页</a>
二、在ShiroFilterFactoryBean中添加
//受权拦截,须要放在认证的上面。某个用户有add的权限才能够访问。 filterMap.put("/user/add","perms[user:add]");
三、启动测试,登陆访问add,发现出现401错误,Unauthorized未受权
当咱们实现权限拦截后,shiro会自动跳转到未受权的页面,但咱们没有这个页面,因此401
注意:ShiroFilterFactoryBean中完成受权配置后,此处访问add必需要先登陆,未登陆的状况下,受权的perms参数也会直接跳转到login,要求你先登陆。
四、配置一个未受权的提示的页面,增长一个controller提示。而后在ShiroFilterFactoryBean中添加配置
@RequestMapping("/noauth") @ResponseBody public String noAuth(){ return "未经受权不能访问此页面"; }
shiroFilterFactoryBean.setUnauthorizedUrl("/noauth");
五、再次启动访问测试。拦截成功。
六、ShiroFilterFactoryBean中完整代码
//建立ShiroFilterFactoryBean @Bean(name ="shiroFilterFactoryBean") public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){ ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); //设置安全管理器 shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager); /* * 经常使用的有以下过滤器: * anon: 无需认证就能够访问 * authc: 必须认证才能够访问 * user: 若是使用了记住我功能就能够直接访问 * perms: 拥有某个资源权限才能够访问 * role: 拥有某个角色权限才能够访问 */ //注意此处使用的是LinkedHashMap,是有顺序的,shiro会按从上到下的顺序匹配验证,匹配了就再也不继续验证 //因此上面的url要苛刻,宽松的url要放在下面,尤为是"/**"要放到最下面,若是放前面的话其后的验证规则就没做用了。 Map<String, String> filterMap = new LinkedHashMap<>(); //受权拦截,须要放在认证的上面。某个用户有add的权限才能够访问。 //受权,正常状况下,没有受权会跳转到未受权页面 filterMap.put("/user/update","perms[user:update]"); filterMap.put("/user/add","perms[user:add]"); //认证 // filterMap.put("/user/add","authc"); // filterMap.put("/user/update","authc"); //修改到要跳转的login页面; shiroFilterFactoryBean.setLoginUrl("/login"); //跳转到未受权的请求页面 shiroFilterFactoryBean.setUnauthorizedUrl("/noauth"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap); return shiroFilterFactoryBean; }
而如何给某个用户具体受权,进行权限的操做等。本文不作细致的讲解,由于这须要结合项目中具体的业务逻辑来进行编写,而且须要进行数据库权限表等一系列的设计操做等,相较复杂。
在文章集成SpringBoot部分,说了Shiro的导入Pom依赖的两种方式。其中第二种可能会报错。
报错内容一:
可能会出现启动时没法找到shiroFilterFactoryBean的bean
*************************** APPLICATION FAILED TO START *************************** Description: Method filterShiroFilterRegistrationBean in org.apache.shiro.spring.config.web.autoconfigure.ShiroWebFilterConfiguration required a bean named 'shiroFilterFactoryBean' that could not be found. Action: Consider defining a bean named 'shiroFilterFactoryBean' in your configuration.
解决:若是在Shiro配置类中的ShiroFilterFactoryBean的方法名没有命名为shiroFilterFactoryBean,须要手动在@Bean上添加上名字,不然没法识别注入。以下
@Bean(name = "shiroFilterFactoryBean")
报错内容二:
项目没法启动,报没有authorizer的bean的错误: No bean named 'authorizer' available
解决:本身在配置类中配置
@Configuration public class ShiroConfig { @Bean public Authorizer authorizer(){ return new ModularRealmAuthorizer(); } }
具体参考这篇文章:https://www.cnblogs.com/insan...
本文只是Shiro的入门学习,适合于新手初探Shiro,有个大体的了解与应用。
主要讲了一些Shiro的基础知识,以及集成SpringBoot简单应用的流程和配置,而对于Shiro的一些源码等内容并无进行一个分析和讲解,可能会在以后的进阶文章中补充。
学习建议:
一、Shiro的一些配置类和简单业务逻辑代码是死的,大可不用花不少精力去死记那些单词又长的代码,在初学以后应总结成本身的环境模板,用时即取便可,多将精力放在Shiro的整个认证受权的运行过程上,了解是怎么进行的。
二、Shiro可定制化程度很高,在不少涉及到权限的项目中都有运用,可自行去码云或GitHub上寻找优质的(star数较高)的开源项目(如权限管理系统)来进行学习,看实际的项目中Shiro的应用。
三、本文文中涉及的代码。须要的话自行下载:连接:https://pan.baidu.com/s/1xicF...
提取码:quo0
参考:
一、http://shiro.apache.org/index... Shiro官网
二、https://www.bilibili.com/vide... 碰见狂神说,【狂神说Java】SpringBoot整合Shiro框架
三、https://zhuanlan.zhihu.com/p/... 我没有三颗心脏,Shiro安全框架【快速入门】就这一篇!公众号wmyskxz
四、https://juejin.im/post/684490... 码农小胖哥,Spring Security 实战干货: RBAC权限控制概念的理解
五、http://www.woshipm.com/pd/115... 珣玗琪,RBAC模型:基于用户-角色-权限控制的一些思考
六、https://how2j.cn/k/shiro/shir... How2j Shiro系列教材
谢谢您看完这篇技术文章
若是能对您有所帮助
那将是一件很美好的事情
保持好奇心的终身学习也是极棒的事
愿世界简单又多彩
转载请注明出处
——纸飞机