Springboot与Shiro的整合
Springboot使用的页面模板是thyme leaf,它比jsp多一些特定的标签,可以动态或者静态显示文本内容
1、spring boot中导入thymeleaf页面模板
(1) 在pom.xml文件引入thymeleaf
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
(2)在application.properties(application.yml)文件中配置thymeleaf
thymeleaf: cache: false # 关闭页面缓存 encoding: UTF-8 # 模板编码 prefix: classpath:/templates/ # 页面映射路径 suffix: .html # 试图后的后缀 mode: HTML5 # 模板模式
(3)测试导入的thymeleaf页面
/** * 测试thymeleaf */ @RequestMapping("/testThymeleaf") public String testThymeleaf(Model model){ //把数据存入model model.addAttribute("name","qmh"); //model.addAttribute默认返回的.html文件,所以这里return的是test.html return "/test"; }
取测试页面的name名字,建立test.html页面(注意:spring boot建立html的地方为src/main/java/resources/temelates中)
<!DOCTYPE html> <!-- ~ /* ~ * Copyright (c) 2019. tdc.shangri-la.com. All Rights Reserved. ~ */ --> <br lang="en" xmlns:th="http://www.w3.org/1999/xhtml" > <head> <meta charset="UTF-8"> <title>测试thymeleaf的使用</title> </head> <body> <hr> <!--获取name--> <h2 th:text="${name}"></h2> </hr> </body> </html>
2.Springboot与Shiro整合实现用户认证
2.1shiro核心ApI
* subject(包括用户登录,注销、授权的方法,需要关联securityManager)
* securityManager:安全管理器(需要连接realm)
* realm:shiro连接数据的桥梁
2.2.Springboot与Shiro整合
(1)导入shiro与spring的依赖
<!--导入shiro与spring整合的依赖--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.0</version> </dependency>
(2)自定义realm类,realm类用来编写认证或授权的方式方法或者逻辑,完成shiro与其他应用的数据连接
realm类需要继承AuthorizingRealm ,实现doGetAuthorizationInfo(授权)、doGetAuthenticationInfo(认证)的方法
package com.toxic.anepoch.boot2.controller.web.Shiro; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; /** * Title: * Description:自定义realm类,实现shiro框架中realm部分的逻辑操作, * (1)与securitymanager部分的关联 * (2)完成shiro与数据库的连接 * * @author py * @date 2020/4/16 20:07. * */ public class UserRealm extends AuthorizingRealm { //执行授权逻辑 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("执行授权认证"); return null; } //执行认证逻辑 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("执行逻辑认证"); return null; } }
(2)编写shiro的配置类ShiroConfigTest
创建三个对象ShiroFilterFactoryBean、DefaultSecurityManager、realm
package com.toxic.anepoch.boot2.controller.web.Shiro; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.LinkedHashMap; import java.util.Map; /** * Title: * @author py * @date 2020/4/16 19:42. */ //@Configuration 注解,表示声明该类为Spring 的配置类 @Configuration public class ShiroConfigTest { // 创建三个对象 /** * 创建ShiroFilterFactoryBean(关联manager) */ //@Bean public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager securityManager ){ ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); return shiroFilterFactoryBean; } /** *创建DefaultSecurityManager(关联realm对象) */ //@Bean //@qualifier,qualifier的意思是合格者,通过这个标示,表明了哪个实现类才是我们所需要的,该注解是用来消除依赖注入冲突的 public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(userRealm); return securityManager; } /** *创建realm(自定义一个realm实体类,通过实体类进行调用) */ //创建一个方法,方法返回对象即我们要创建的对象 UserRealm ,返回该对象的实例。在方法上打上注解@Bean即表示声明该方法返回的实例是受 Spring 管理的 Bean,便于其他方法使用 @Bean public UserRealm getUserBealm(){ UserRealm userRealm = new UserRealm(); return userRealm; } }
2.3Shiro常用的内置过滤器实现url页面的拦截
* 常用的一些过滤器等级:
* anon:无需认证(登录)就可以访问
* authc:必须认证才可以访问
* user:如果使用rememberMe的功能,可以直接访问
* perms;该资源必须授予资源权限才可以访问
* role;该资源必须授予角色权限才可以访问
package com.toxic.anepoch.boot2.controller.web.Shiro; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.LinkedHashMap; import java.util.Map; //@Configuration 注解,表示声明该类为Spring 的配置类 @Configuration public class ShiroConfigTest { /** * 创建ShiroFilterFactoryBean */ @Bean public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager securityManager ){ ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); //设置安全管理器等级 shiroFilterFactoryBean.setSecurityManager(securityManager); //添加shiro的内置过滤器,实现url的相关的拦截 /** * 常用的一些过滤器等级: * anon:无需认证(登录)就可以访问 * authc:必须认证才可以访问 * user:如果使用rememberMe的功能,可以直接访问 * perms;该资源必须授予资源权限才可以访问 * role;该资源必须得到角色权限才可以访问 */ //创建一个map集合,用来参访需要拦截的url路径(controller中 @RequestMapping("/"))即及改路径的过滤等级,这里创建LinkedHashMap集合,是为了存入的数据具有顺序 Map<String,String> filterMap = new LinkedHashMap<>(); //若拦截生效后默认跳转到login.jsp filterMap.put("/add","authc"); filterMap.put("/update","authc"); /* 要想对user下所有的url都过滤,那么可以用* (*通配符) filterMap.put("/user/*","authc"); */ /* *想对某一个url做放行处理可以用以下方法 * filterMap.put("/testThymeleaf","anon"); * */ //修改拦截的时侯跳转的页面(这里用的authc等级,就可以理解为要先完成登录),登录页面都从controller层进行关联,toLogin是controller的方法,用来访问login登录页面 shiroFilterFactoryBean.setLoginUrl("/toLogin"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap); return shiroFilterFactoryBean; } /** *创建DefaultSecurityManager(关联realm对象) */ @Bean(value = "securityManager") public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(userRealm); return securityManager; } /** *创建realm(自定义一个realm实体类,通过实体类进行调用) */ //在创建一个方法,方法返回对象即我们要创建的对象 UserRealm , 返回该对象的实例。在方法上打上注解@Bean即表示声明该方法返回的实例是受 Spring 管理的 Bean,便于其他方法使用 @Bean(value = "userRealm") public UserRealm getUserBealm(){ UserRealm userRealm = new UserRealm(); return userRealm; } }
2.4Shiro用户登录操作
创建一个登陆页面:
<!DOCTYPE html> <!-- ~ /* ~ * Copyright (c) 2019. tdc.shangri-la.com. All Rights Reserved. ~ */ --> <html lang="en" xmlns:th="http://www.w3.org/1999/xhtml"> <head> <meta charset="UTF-8"> <title>用户的登录页面</title> </head> <body> <h3>登录</h3> <!--获取controller中用户名或是密码的错误信息--> <p color="red" th:text="${msg}"></p> <form method="post" action="login"> 用户名:<input type="text" name="name"> </br> 密码:<input type="password" name="password"></br> <input type="submit" value="登录"> </form> </body> </html>
controller的登录逻辑:
//登陆的逻辑处理,接受表单中的name,password两个参数 @RequestMapping("/login") public String login(String name,String password,Model model){ //使用shiro编写认证操作 //1.获取subject对象 Subject subject = SecurityUtils.getSubject(); //2.封装用户的数据,将表单中的数据传递给token对象 UsernamePasswordToken token = new UsernamePasswordToken(name,password); //3.执行登录方法,这里使用try...catch来判断是否登录成功,若没有异常则登陆成功,有异常在进行判断并抛出异常 try { subject.login(token); System.out.println("登陆成功"); //登陆成功跳转到主页test.html (redirect:重定向) return "redirect:/hello/testThymeleaf"; } catch (UnknownAccountException e) { //用户名不存在,信息返回到html页面 model.addAttribute("msg","用户名不存在"); //返回到登陆页面 return "login"; } catch (IncorrectCredentialsException e) { //密码错误,信息返回到html页面 model.addAttribute("msg","密码错误"); //返回到登陆页面 return "login"; }
package com.toxic.anepoch.boot2.controller.web.Shiro; import org.apache.shiro.authc.*; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; /** * Title: * Description:自定义realm类,实现shiro框架中realm部分的逻辑操作, * (1)与securitymanager部分的关联 * (2)完成shiro与数据库的连接 * * @author py * @date 2020/4/16 20:07. * */ public class UserRealm extends AuthorizingRealm { //执行授权逻辑 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("执行授权认证"); return null; } //执行认证逻辑 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken ) throws AuthenticationException { System.out.println("执行逻辑认证"); //假设数据库的用户名和密码分别是:qiu,123456 String name="qiu"; String password="123456"; //编写shiro的判断逻辑,判断用户名和密码是否正确 //authenticationToken类型强制转换成controller层中的UsernamePasswordToken类型,用来存放name和password // (controller中token的作用是:封装用户的数据,将表单中的数据传递给token对象,具体操作:UsernamePasswordToken token = new UsernamePasswordToken(name,password);) UsernamePasswordToken Token = (UsernamePasswordToken) authenticationToken; if(!Token.getUsername().equals(name)){ System.out.println("用户名不存在"); return null;//shiro的底层会抛出UnknownAccountException异常 }else{ //判断密码,直接创建AuthorizationInfo对象的子对象,传入password参数就可以判断密码是否一致 return new SimpleAuthenticationInfo("",password,""); } } }
2.5整合myBatis实现用户的登录
2.5.1导入myBatis的相关的依赖
<!-- mybatis-plus begin mybtis启动器--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatisplus-spring-boot-starter</artifactId> <version>${mybatisplus-spring-boot-starter.version}</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus</artifactId> <version>${mybatisplus.version}</version> </dependency> <!-- mybatis-plus end --> <!--sharding-jdbc start --> <dependency> <groupId>io.shardingjdbc</groupId> <artifactId>sharding-jdbc-spring-boot-starter</artifactId> <version>${sharding-sphere.version}</version> </dependency> <!-- sharding-jdbc end --> <!--mysql start--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql-connector-java.version}</version> <scope>runtime</scope> </dependency> <!--mysql end--> <!-- druid 数据库连接池--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>${com.alibaba.druid.version}</version> </dependency> </dependencies>
2.5.2创建application.properties或application.yml文件,用来设置一些参数
//数据库的配置 datasource: type: com.alibaba.druid.pool.DruidDataSource #druid相关配置 driver-class-name: com.mysql.cj.jdbc.Driver #基本属性 url: jdbc:mysql: username: password: //mybatis的配置 mybatis: mapper-locations: classpath:mybatis/mapper/*.xml type-aliases-package: com.toxic.anepoch.boot2.service.bean
2.5.3在com.toxic.anepoch.boot2.service.bean包下创建user实体类
package com.toxic.anepoch.boot2.service.bean; /** * Title: * Description: * * @author py * @date 2020/4/19 22:15. */ public class User { private int id; private String name; private String password; public User() { } public User(int id, String name, String password) { this.id = id; this.name = name; this.password = password; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + ", password='" + password + '\'' + '}'; }
2.5.4创建mapper接口实现与数据库的关联并创建mapper.xml文件
package com.toxic.anepoch.boot2.service.dao; import com.toxic.anepoch.boot2.service.bean.User; import org.apache.ibatis.annotations.Param; import org.springframework.stereotype.Repository; /** * Title:查询用户 * Description: * * @author py * @date 2020/4/19 22:22. */ @Repository //@Repository用在持久层的接口上,这个注解是将接口的一个实现类交给spring管理。 public interface UserMapper { User findByName(@Param(value = "name") String name); }
<?xml version="1.0" encoding="UTF-8" ?> <!-- ~ /* ~ * Copyright (c) 2019. tdc.shangri-la.com. All Rights Reserved. ~ */ --> <!DOCTYPE mapper PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!-- 不使用namespace的话sql搜索定位会比较方便 --> <mapper namespace="com.toxic.anepoch.boot2.service.dao.UserMapper"> <resultMap id="UserInfoResultMap" type="com.toxic.anepoch.boot2.service.bean.User"> <result property="id" column="id"/> <result property="name" column="name"/> <result property="password" column="password"/> </resultMap> <!-- 用于select查询公用抽取的列 --> <sql id="UserInfoColumns"> <![CDATA[ id,name,password ]]> </sql> <select id="findByName" resultMap="UserInfoResultMap"> SELECT <include refid="UserInfoColumns" /> <![CDATA[ FROM user WHERE name = #{name} ]]> </select> </mapper>
2.5.5编写service接口和实现类,用于调用mepper接口
package com.toxic.anepoch.boot2.service.service; import com.toxic.anepoch.boot2.service.bean.User; /** * Title: * Description: * * @author py * @date 2020/4/19 22:30. */ public interface UserService { /** *通过名字进行查询 */ User findByName(String name); }
package com.toxic.anepoch.boot2.service.service.impl; import com.toxic.anepoch.boot2.service.bean.User; import com.toxic.anepoch.boot2.service.dao.UserMapper; import com.toxic.anepoch.boot2.service.service.UserService; import org.springframework.stereotype.Service; import javax.annotation.Resource; /** * Title: * Description: * * @author py * @date 2020/4/19 22:34. */ @Service public class UserServiceImpl implements UserService{ @Resource // @Resource注入mapper接口 private UserMapper userMapper; @Override public User findByName(String name) { return userMapper.findByName( name); } }
2.5.5编写Biz接口和实现类,用于调用service接口和业务的创建
package com.toxic.anepoch.boot2.biz.business; import com.toxic.anepoch.boot2.service.bean.User; /** * Title: * Description: * * @author py * @date 2020/4/19 22:58. */ public interface UserBiz { User findByName(String name); }
/** * Title: * Description: * * @author py * @date 2020/4/19 22:59. */ @Component public class UserBizImpl implements UserBiz{ @Resource private UserService userService; @Override public User findByName(String name) { return userService.findByName(name); } }
对UserRealm类进行修改
public class UserRealm extends AuthorizingRealm { //执行授权逻辑 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("执行授权认证"); return null; } @Autowired //@Autowired注入UserBiz接口 private UserBiz userBiz; //执行认证逻辑 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken ) throws AuthenticationException { System.out.println("执行逻辑认证"); //假设数据库的用户名和密码分别是:qiu,123456 // String name="qiu"; // String password="123456"; //编写shiro的判断逻辑,判断用户名和密码是否正确 //authenticationToken类型强制转换成controller层中的UsernamePasswordToken类型,用来存放name和password // (controller中token的作用是:封装用户的数据,将表单中的数据传递给token对象,具体操作:UsernamePasswordToken token = new UsernamePasswordToken(name,password);) UsernamePasswordToken Token = (UsernamePasswordToken) authenticationToken; User user = userBiz.findByName(Token.getUsername()); if(user==null){ System.out.println("用户名不存在"); return null;//shiro的底层会抛出UnknownAccountException异常 }else{ //判断密码,直接创建AuthorizationInfo对象的子对象,传入password参数就可以判断密码是否一致 return new SimpleAuthenticationInfo("",user.getPassword(),""); } } }
3.spring boot与shiro整合实现用户授权
3.1使用shiro的内置过滤器拦截资源
常用的授权过滤器:perms,role,授权过滤器与认证过滤器不同的是:授权过滤器需要加授权字符串,这里给add接口添加授权
//添加授权过滤器,授权字符串,授权字符串可以任意的指定 filterMap.put("/add","perms:[user:add]");
未授权过滤器生效后,会自动跳转到默认的未授权页面,当然也可以自定义一个未授权的页面
需要在controller层建立一个/noAuth路径去访问noAuth页面
//设置未授权页面,授权过滤器生效,跳转到noAuth页面 shiroFilterFactoryBean.setUnauthorizedUrl("/noAuth");
3.2编写资源授权逻辑
public class UserRealm extends AuthorizingRealm { //执行授权逻辑 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("执行授权认证"); //给资源授权 SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); //添加getShiroFilterFactoryBean方法中设置的授权字符串 info.addStringPermission("user:add"); return info; } }
3.3关联数据库实现动态授权
从新编写一个select方法,【包括:UserMapper.xml, UserMapper实体类(拥有id,name,password,perms),UserService接口、UserServiceImpl实现类、UserBiz接口、UserBizImpl实现类、controller层的授权测试方法】,此过程与验证时通过名字进行查询的步骤相似
UserRealm类中的授权逻辑的修改
//执行授权逻辑 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("执行授权认证"); //给资源授权,创建AuthorizationInfo接口的实现类:SimpleAuthorizationInfo SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); //添加getShiroFilterFactoryBean方法中设置的授权字符串 //info.addStringPermission("user:add"); //到数据库查询登录用户的授权字符串 Subject subject = SecurityUtils.getSubject(); //获取当前登录对象 User user =(User) subject.getPrincipal(); User dbUser = userBiz.findById(user.getId()); info.addStringPermission(dbUser.getPerms()); return info; }
3.4thymeleaf标签与shiro进行整合使用
3.4.1导入thyme leaf扩展的坐标
<!--导入thymeleaf对shiro扩展的坐标--> <dependency> <groupId>com.github.theborakompanioni</groupId> <artifactId>thymeleaf-extras-shiro</artifactId> <version>2.0.0</version> </dependency>
3.4.2在ShiroConfigTest配置类中添加配置ShiroDialect的方法,用于thymeleaf标签与shiro进行整合使用
//ShiroDialect @Bean public ShiroDialect getDhiroDialect(){ return new ShiroDialect(); }
3.5在thymeleaf页面上使用shiro标签,实现的效果是用户登录成功后先判断此用户是否有授权权限,有相应的授权就展示出来,没有则不进行展示
<body> <h2 color = "red" th:text="${name}"></h2> </hr> <div shiro:HasPermissionAttrProcessor="user:add"><!--判断用户是否有add权限update,有则可以访问该内容--> 进入用户的添加操作;<a href="add">增加用户</a></br> </div> <div shiro:HasPermissionAttrProcessor="user:update"> 进入用户的更新操作:<a href="update">更新用户</a> <!--判断用户是否有权限--> </div> </body>