Springboot与Shiro的整合
Posted QMH
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Springboot与Shiro的整合相关的知识,希望对你有一定的参考价值。
https://www.jianshu.com/p/d0b354cc5147
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关联数据库实现动态授权
以上是关于Springboot与Shiro的整合的主要内容,如果未能解决你的问题,请参考以下文章