SpringBoot整合Shiro实现认证与授权
简介
什么是shiro?
Shiro是一个强大的简单易用的Java安全框架,主要用来更便捷的认证,授权,加密,会话管理。Shiro首要的和最重要的目标就是容易使用并且容易理解。
首先明白几个概念:shiro三个核心组件
- Subject : 用户主体,即当前操作用户(它把操作交给SecurityManager)。
所有Subject都需要SecurityManager,当你与Subject进行交互,这些交互行为实际上被转换为与SecurityManager的交互。 - SecurityManager : 安全管理器或安全管理员(它关联 Realm)。
Shiro架构的核心,它就像Shiro内部所有原件的保护伞。然而一旦配置了SecurityManager,SecurityManager就用到的比较少,开发者大部分时间都是和Subject打交道。 - Realm : 是Shiro连接数据的桥梁。
充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行==认证 (登录) ==和 ==授权(访问控制)==验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。从这个意义上讲,Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个。
shiro能做什么?
shiro认证工作流程:
认证流程:
授权流程:
Shiro相关类介绍:
(1)Authentication :认证 ---- 用户登录
(2)Authorization :授权 — 用户具有哪些权限
(3)Cryptography :安全数据加密
(4)Session Management :会话管理
(5)Web Integration :web系统集成
(6)Interations: 集成其它应用,spring、缓存框架
Shiro 特点:
(1)易于理解的 Java Security API;
(2)简单的身份认证(登录),支持多种数据源(LDAP,JDBC,Kerberos,ActiveDirectory 等);
(3)对角色的简单的签权(访问控制),支持细粒度的签权;
(4)支持一级缓存,以提升应用程序的性能;
(5)内置的基于 POJO 企业会话管理,适用于 Web 以及非 Web 的环境;
(6)异构客户端会话访问;
(7)非常简单的加密 API;
(8)不跟任何的框架或者容器捆绑,可以独立运行
SpringBoot整合Shiro实现认证与授权验证
第一步:pom文件导入相关依赖
<!--shiro与spring整合依赖-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<!--导入mybatis相关的依赖-->
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--springboot的mybatis启动器-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<!--这里数据源使用:德鲁伊-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.9</version>
</dependency>
<!--导入thymeleaf模板,使用模板页面进行测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
第二步,编写一个shiro配置类
因为shiro有三个核心的组件,并且shiro主要通过过滤器进行拦截请求来实现安全控制。因此,编写一个配置类依次创建对应的bean对象,放入spring容器。
/**
* shiro的配置类
*/
@Configuration
public class ShiroConfig {
/**
* 创建ShiroFilterFactoryBean
*/
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//设置安全管理器(关联SecurityManager)
shiroFilterFactoryBean.setSecurityManager(securityManager);
//============== 开始配置拦截:==================
//1.添加Shiro内置过滤器
/**
* Shiro内置过滤器,可以实现权限相关的拦截器。用于拦截需要权限才能访问资源的请求。
* 常用的过滤器:
* anon: 无需认证(登录)就可以访问。允许匿名身份访问
* authc: 必须认证才可以访问
* user: 如果使用rememberMe的功能可以直接访问
* perms: 该资源必须得到资源权限才可以访问
* role: 该资源必须得到角色权限才可以访问
*/
Map<String,String> filterMap = new LinkedHashMap<String,String>();
//添加认证过滤器
//filterMap.put("/api/add","authc");
//filterMap.put("/api/update","authc");
filterMap.put("/api/testThymeleaf","anon"); // 放行该请求。
filterMap.put("/api/login","anon"); // 放行该请求。
//添加授权过滤器
filterMap.put("/api/add","perms[user:add]");// 需要权限才能访问。并且要放在拦截所有的上面才有效。
filterMap.put("/api/update","perms[user:update]");
filterMap.put("/api/*","authc"); // 拦截/api/所有请求
//authc模式拦截后默认跳转到login.jsp页面,无论有没有该页面。
// 修改调整的登陆页面
shiroFilterFactoryBean.setLoginUrl("/api/toLogin");
//设置未授权提示页面
shiroFilterFactoryBean.setUnauthorizedUrl("/api/unAuthorized");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
//====================================结束==========================================
return shiroFilterFactoryBean;
}
/**
* 创建DefaultWebSecurityManager
*/
@Bean(name="securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//关联Realm
securityManager.setRealm(userRealm);
return securityManager;
}
/**
* 创建Realm
* 我们可以先自定义一个Realm类
*/
@Bean(name = "userRealm")
public UserRealm getRealm(){
return new UserRealm();
}
}
第三步,自定义Realm类:一定要继承AuthorizingRealm类,并重写两个方法
public class UserRealm extends AuthorizingRealm {
@Autowired
UserService userService;
/**
* 执行授权逻辑
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行授权逻辑");
//给资源进行授权
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//从数据库中获取授权字符串
Subject subject = SecurityUtils.getSubject();
//subject.getPrincipal()获取的实际上是认证逻辑中return new SimpleAuthenticationInfo(user,user.getPassword(),"")第一个参数
User user = (User) subject.getPrincipal();
User dbUser = userService.findByName(user.getName());
//添加资源的授权字符串
info.addStringPermission(dbUser.getPerm());
return info;
}
/**
* 执行认证逻辑
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行认证逻辑");
//编写shiro判断逻辑,判断用户名和密码
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
//数据库中的username:admin和 password:123456
User user = userService.findByName(token.getUsername());
//1.判断用户名
if (user==null){
//用户名不存在
return null; // shiro底层会抛出UnknowAccountException异常;
}
//2.判断密码。我们只需要返回AuthenticationInfo的一个子类SimpleAuthenticationInfo
//第一个参数:返回登录逻辑中login方法结果的数据;第二个参数:数据库密码;第三个参数:shiro的名字
//return new SimpleAuthenticationInfo("",user.getPassword(),"");
return new SimpleAuthenticationInfo(user,user.getPassword(),"");
}
}
Controller代码:
@Controller
@RequestMapping("/api")
public class HelloController {
//测试主页
@RequestMapping("/testThymeleaf")
public String testThymeleaf(Model model){
model.addAttribute("name","SpringBooot整合Shiro");
return "test";
}
@RequestMapping("/add")
public String add(){
return "/user/add";
}
@RequestMapping("/update")
public String update(){
return "/user/update";
}
//负责跳转到登陆页面
@RequestMapping("/toLogin")
public String toLogin(){
return "login";
}
//负责跳转到未授权页面
@RequestMapping("/unAuthorized")
public String unAuthorized(){
return "/user/unAuthorized";
}
//登录处理逻辑
@RequestMapping("/login")
public String login(String name,String password,Model model){
System.out.println("name:"+name+" password: "+password);
/**
* 使用Shiro编写认证(登录)操作
*/
//1,获取Subject
Subject subject = SecurityUtils.getSubject();
//2,封装用户数据,生成一个令牌
UsernamePasswordToken token = new UsernamePasswordToken(name,password);
//3.执行登录方法
try {
/*执行该方法时,会自动执行我们定义的UserRealm里doGetAuthenticationInfo方法认证逻辑代码!
为什么?通过查看源码 它底层最终会调用到DefaultWebSecurityManager的login()方法,
而DefaultWebSecurityManager关联了Realm且该方法内使用了AuthenticationInfo对象
故会执行Realm里的认证逻辑。我们自定义了一个Realm类(UserRealm),重写了认证逻辑和授权逻辑方法。
*/
subject.login(token);
//登录成功
return "redirect:/api/testThymeleaf";
} catch (UnknownAccountException e) {
//e.printStackTrace();
//登录失败
model.addAttribute("msg","用户名不存在");
return "login"; //跳回login.html页面。
} catch (IncorrectCredentialsException e){
model.addAttribute("msg","密码错误");
return "login"; //跳回login.html页面。
}
}
}
application.properties:
#数据库配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/test_shiro?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
#下面两个配置可有可无
#配置别名允许实体类使用别名即不区分大小写
mybatis.type-aliases-package=com.itnet.myspringboot.domain
# mybatis xml文件的加载地址
mybatis.mapper-locations=classpath:com/itnet/myspringboot/mapper/*Mapper.xml
mapper接口文件:
@Mapper
@Repository
public interface UserMapper {
public User findByName(String name);
}
mapper文件:
<?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">
<!-- namespace必须是UserMapper接口的权限的类目,不然没办法自动绑定 -->
<mapper namespace="com.itnet.myspringboot.mapper.UserMapper">
<!-- 因为properties文件配置了别名,所以resultType不用写实体类的全限定类名了 -->
<select id="findByName" parameterType="String" resultType="user">
select * from user where name = #{name}
</select>
</mapper>
Service实现类:
@Service
public class UserServiceImpl implements UserService {
@Autowired
UserMapper userMapper;
@Override
public User findByName(String name) {
return userMapper.findByName(name);
}
}
目录结构:
测试:
点击“添加页面”,跳转到登录页面,我输入admin 123456进行登录
数据库里: admin只拥有user:add
权限。Jason只拥有user:update
权限。
thymeleaf整合shiro权限标签
<!--导入thymeleaf模板-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--thymeleaf对shiro的扩展坐标-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
只需在shiro配置类中加上
/**
* 配置ShiroDialect,用于thymeleaf和shiro标签配合使用
*/
@Bean
public ShiroDialect getShiroDialecct(){
return new ShiroDialect();
}
在前端页面就可以使用shiro标签了
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>testThymeleaf</title>
</head>
<body>
<h3 th:text="${name}"></h3>
<hr/>
<!--拥有该权限才会显示该内容-->
<div shiro:hasPermission="user:add">
<a href="/api/add">添加页面</a>
</div>
<!--拥有该权限才会显示该内容-->
<div shiro:hasPermission="user:update">
<a href="/api/update">更新页面</a>
</div>
</body>
</html>
拓展:
常见权限框架
Shiro
Apache Shiro是Java的一个安全框架。目前,使用Apache Shiro的人越来越多,因为它相当简单,对比Spring Security,可能没有Spring Security做的功能强大,但是在实际工作时可能并不需要那么复杂的东西,所以使用小而简单的Shiro就足够了。对于它俩到底哪个好,这个不必纠结,能更简单的解决项目问题就好了。
Spring Security
Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。它是一个轻量级的安全框架,它确保基于Spring的应用程序提供身份验证和授权支持。它与Spring MVC有很好地集成,并配备了流行的安全算法实现捆绑在一起。安全主要包括两个操作“认证”与“验证”(有时候也会叫做权限控制)。“认证”是为用户建立一个其声明的角色的过程,这个角色可以一个用户、一个设备或者一个系统。“验证”指的是一个用户在你的应用中能够执行某个操作。在到达授权判断之前,角色已经在身份认证过程中建立了。
Shiro和Spring Security比较
(1)Shiro比Spring更容易使用,实现和最重要的理解
(2)Spring Security更加知名的唯一原因是因为品牌名称
(3)“Spring”以简单而闻名,但讽刺的是很多人发现安装Spring Security很难
(4)Spring Security却有更好的社区支持
(5)Apache Shiro在Spring Security处理密码学方面有一个额外的模块
(6)Spring-security 对spring 结合较好,如果项目用的springmvc ,使用起来很方便。但是如果项目中没有用到spring,那就不要考虑它了。
(7)Shiro 功能强大、且 简单、灵活。是Apache 下的项目比较可靠,且不跟任何的框架或者容器绑定,可以独立运行