SpringBoot实现基于shiro安全框架的,配合thymeleaf模板引擎的用户认证和授权
Posted 新来的大狮
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot实现基于shiro安全框架的,配合thymeleaf模板引擎的用户认证和授权相关的知识,希望对你有一定的参考价值。
SpringBoot实现基于shiro安全框架的,配合thymeleaf模板引擎的用户认证和授权,及其思路介绍和流程详解
1 用户、角色和权限的概念及关系
- 用户:应用系统的具体操作者。
- 角色:便于复用的权限集合。
- 权限:规定了的一系列的访问规则,如:系统资源的访问权限,系统功能的操作权限等。
一般用户具体可具有多种角色(或:角色被分配给用户),角色可包含多种权限。借助不同的角色作为桥梁,使不同的用户具有不同的权限。
具体三者关系(通常情况):
- 一个用户可以拥有不同的角色,一个角色可以被授予给不同的用户。(即:用户和角色的关系:多对多)
- 一个角色可以拥有不同的权限,一个权限可以被分配给不同的角色。(即:角色和权限的关系:多对多)
2 shiro安全框架部分基本功能
2.1 用户认证
系统对用户身份进行验证合法性的过程。常见形式如:输入用户名密码登录系统。
2.2 用户授权
认证用户合法性后,对用户具有的角色授予相应权限的过程。
3 不同用户系统功能的差异性实现
3.1 准备工作-引入shiro相关依赖
<!--shiro权限管理框架-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<!--thymeleaf对shiro的扩展-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
3.2 基于代码的shiro框架的简单架构与运行机制介绍
- 创建shrio过滤工厂:ShiroFilterFactoryBean
- 创建shiro安全管理器:SecurityManager
- 创建自定义的领域类:自定义Realm类(继承shiro的授权抽象类),其实现了自定义的用户认证和授权逻辑。
shiro整体执行逻辑:
需要进行认证或授权时,shiro框架根据创建的过滤工厂,为其指配好创建的安全管理器,然后为安全管理器设置自定义认证和授权逻辑的领域类,最终实现业务的安全逻辑。
其中:自定义Realm类的
- doGetAuthorizationInfo(PrincipalCollection principalCollection) 用于实现授权逻辑
- doGetAuthenticationInfo(AuthenticationToken authenticationToken) 用于实现认证逻辑
3.3 具体shiro环境配置代码
3.3.1 shiro环境配置 - ShiroConfig.java
核心编写3个Bean类:过滤工厂(设置需拦截的URL),安全管理器,自定义的Realm类(实现用户认证和用户授权逻辑)
package cn.castle.shiro;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 核心编写3个Bean:过滤工厂,安全管理器,自定义的Realm类
*/
@Configuration
public class ShiroConfig
/**
* 注入这个是是为了在thymeleaf中使用shiro的自定义tag。
*/
@Bean(name = "shiroDialect")
public ShiroDialect shiroDialect()
return new ShiroDialect();
/**
* 1. shiro过滤工厂 URL地址过滤器
* @param securityManager
* @return
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager)
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 设置securityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 设置默认登录url
shiroFilterFactoryBean.setLoginUrl("/login");
// // 设置主页url
shiroFilterFactoryBean.setSuccessUrl("/");
// // 设置未授权的url
shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");
/**
* shiro内置过滤器
* 常用过滤器:
* anon: 无需登录
* authc: 必须认证
* user: 使用rememberMe 可以直接访问
* perms: 必须获得资源权限
* role: 必须获得角色权限
*/
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/student", "authc");
filterChainDefinitionMap.put("/study", "perms[system]");
filterChainDefinitionMap.put("/books", "anon");
// 其余url全部拦截,必须放在最后
// filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
/**
* 2. 自定义安全管理器
*/
@Bean
public SecurityManager getSecurityManager()
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//关联自定义的realm类
securityManager.setRealm(userRealm());
return securityManager;
/**
* 3. 自定义的Realm类实现认证和授权
* @return
*/
@Bean
public UserRealm userRealm()
return new UserRealm();
3.3.2 自定义Realm类实现 - UserRealm.java
3.3.2.1 认证逻辑思路
(shiro的认证核心思想:拦截指定URL)
(自定义的认证核心逻辑:查询数据库,若用户名和密码一致则认证通过)
(实现认证需要的配合:过滤工厂拦截指定URL,以及controller层对shiro认证后返回结果的页面跳转逻辑处理 [ 具体controller层代码详见测试代码3.4.1 ])
- 总流程:借助先前定义的过滤工厂,对指定的URL请求进行拦截,进行用户认证相关逻辑,若未通过用户认证,则无法进行指定URL跳转,而是返回默认进行登录的URL。
- 当在shiro拦截指定URL后,转向用户登录界面。
- 借助AuthenticationToken类型的变量token,将其强制转型为UsernamePasswordToken类型,用于将登录相关数据在进入controller层后,借助该变量将用户名和密码在controller业务逻辑层和shiro用户认证逻辑层传递数据。
- 然后在shiro用户认证逻辑层借助Dao类对数据库根据用户名进行查询,用户不存在返回null,用户存在则进行密码验证逻辑。
- 验证若成功后,shiro用户认证逻辑层将返回SimpleAuthenticationInfo类型给controller层,其构造参数第一个为用户类型,该用户类型的变量指定后 便于进行下一步用户授权时获取当前登录的用户,同时返回相应的登录成功页面。
3.3.2.2 授权逻辑思路(示例自定义的需求的授权过程未使用到URL拦截)
(自定义的授权核心逻辑:查询数据库当前用户所拥有的所有角色,该用户所有角色的所有权限都有哪些,并对当前用户进行授予)
(自定义的业务需求:前端根据预设的权限,若当前用户具有该权限,则开放相应的功能模块)
(实现授权需要的配合:前端页面中thymeleaf写出的模板页面中已经添加带有权限(shiro:hasPermission属性)的标签元素)
- 预先设置前端页面中某些功能模块(某些功能标签)需要一定的权限才可以显示(借助shiro对thymeleaf的扩展,具体代码在测试代码3.4.2中展示)。
- 借助SecurityUtils.getSubject()获取的Subject类型变量,对其调用getPrincipal()方法并对结果强制转型,用于获取当前通过认证的用户。
- 根据获得的用户,向数据库查询其所有的角色,再查询角色的所有权限的Set集,最终通过SimpleAuthorizationInfo变量将其授予给当前用户。(数据库逻辑设定用户具有角色,而角色具有权限)
- 由shiro框架自动完成 根据当前用户是否拥有某些权限而是否开放(显示)某些功能标签。
3.3.2.3 用户认证和授权具体逻辑代码
package cn.castle.shiro;
import cn.castle.dao.PermissionDao;
import cn.castle.dao.RoleDao;
import cn.castle.dao.UserDao;
import cn.castle.pojo.Function;
import cn.castle.pojo.SysRole;
import cn.castle.pojo.SysUser;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.*;
/**
* 自定义Realm类,继承授权Realm类
*/
public class UserRealm extends AuthorizingRealm
@Autowired
UserDao userDao;
@Autowired
RoleDao roleDao;
@Autowired
PermissionDao permissionDao;
/**
* 执行授权逻辑方法
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection)
// 获取当前登录的用户
Subject subject = SecurityUtils.getSubject();
SysUser sysUser = (SysUser) subject.getPrincipal();
// 获取当前用户的所有角色后,根据角色获取所有权限存入set集合中
List<SysRole> roleList = roleDao.getRolesByUserUuid(sysUser.getUuid());
List<Function> functionList = new ArrayList<>();
Set<String> permissionSet = new LinkedHashSet<>();
for(SysRole role: roleList)
functionList.addAll(permissionDao.getFunctionsByRoleUuid(role.getUuid()));
for(Function function: functionList)
permissionSet.add(function.getPression());
// 为当前用户添加读取的所有权限
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
for(String permission: permissionSet)
info.addStringPermission(permission);
return info;
/**
* 执行认证逻辑方法
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
String username = token.getUsername();
String password = String.valueOf(token.getPassword());
List<SysUser> userList = userDao.getSysUserByName(username);
//用户名不存在
if(userList.size() < 1)
// 返回null底层将输出UnknownAccountException
return null;
// 提供数据库的真实密码,由shiro进行用户认证
SysUser sysUser = userList.get(0);
String realPassword = sysUser.getPassword();
return new SimpleAuthenticationInfo(sysUser, realPassword, "");
3.4 具体测试代码
3.4.1 用户认证测试代码
实际操作与内部逻辑:
- 使用浏览器访问指定被过滤的URL资源。(此时未进行登录)
- 由于该URL被shiro框架过滤的原因,且当前用户未认证,第一次访问失败,系统跳转到设定的用户登录页面。
- 用户登录时输入用户名和密码,数据从前端页面传递到controller层,controller层封装后传递给shiro框架的自定义Realm认证逻辑进行认证,并返回认证结果。
- 用户登录成功后(认证成功),shiro返回正确的类型给controller层,再根据controller层逻辑,认证的用户成功进入认证后的界面。
设置将被拦截的指定URL(过滤工厂中设置,详见3.3.1):
filterChainDefinitionMap.put("/student", "authc");
默认在过滤工厂中设置的返回的登录界面:
shiroFilterFactoryBean.setLoginUrl("/login");
被拦截后的前端登录页面的登录表单login.html(使用了thymeleaf模板引擎):
<form role="form" action="/login2">
<h1 class="login" style="text-align: center">授权版登录</h1>
<h3 class="mes" th:text="$message2"></h3>
<p id="result2" hidden th:text="$result"></p>
<div style="text-align: center">
<input type="text" id="username2" name="username88" placeholder="用户名">
</div>
<div style="text-align: center">
<input type="password" id="password2" name="password88" placeholder="密码">
</div>
<div style="text-align: center">
<button type="submit">登录</button>
</div>
</form>
controller层认证及跳转:
需要借助SecurityUtils获取Subject后,为shiro框架执行认证逻辑时 传递 封装着用户名和密码的token对象。
@RequestMapping("/login2")
public String login2(@RequestParam("username88") String username, @RequestParam("password88") String password,
Model model)
// 1. 获取subject对象
Subject subject = SecurityUtils.getSubject();
// 2. 封装用户数据至token
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
// 3. 执行登录方法subject.login,借助shiro执行登录的验证逻辑
try
subject.login(token);
// 使用redirect向控制器发送请求,非直接访问模板页面
return "redirect:/index";
catch (UnknownAccountException e)
model.addAttribute("message2", "用户名不存在");
println("用户不存在");
return "login";
catch (IncorrectCredentialsException e)
println("密码错误");
model.addAttribute("message2", "密码错误");
return "login";
3.4.2 用户授权测试代码
实际操作与内部逻辑;
- 认证过的用户(成功登录后)访问相应的带有权限的URL资源(业务需求:整个html页面中部分功能标签需要权限)。
- 每次加载该带有权限的URL资源时,shiro框架根据自定义Realm类的执行授权逻辑(从数据库取出当前用户的所有权限字符串,添加至SimpleAuthorizationInfo类型变量后返回),对当前用户进行授权。
- 页面根据当前所需权限字符串和用户已拥有权限字符串进行比较,对拥有相应权限(字符串内容一致)的功能标签进行渲染。
注意事项 ①:使用带shiro:hasPermission属性标签的html文件引入的命名空间:
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
注意事项 ② : 使用带shiro:hasPermission属性标签的html文件同时需要ShiroConfig类中创建 支持shiro属性的thymeleaf模板页面 的方言Bean类(ShiroDialect),详见3.3.1-shiro环境配置:
@Bean(name = "shiroDialect")
public ShiroDialect shiroDialect()
return new ShiroDialect();
前端配合的带有权限(shiro:hasPermission属性)的功能模块标签:
<a shiro:hasPermission="system" href="#">
<i class="fa fa-home"></i>
<span class="nav-label">系统管理</span>
<span class="fa arrow"></span>
</a>
<a shiro:hasPermission="system.userPage" class="J_menuItem" style="background-color:#293846" οnmοusemοve="this.style.backgroundColor='#465e79'" οnmοuseοut="this.style.backgroundColor='#293846'" href="/userManage">用户管理</a>
<a shiro:hasPermission="system.rolePage" class="J_menuItem" style="background-color:#293846" οnmοusemοve="this.style.backgroundColor='#465e79'" οnmοuseοut="this.style.backgroundColor='#293846'" href="/toRoleManagement">角色管理</a>
<a shiro:hasPermission="system.functionPage" class="J_menuItem" style="background-color:#293846" οnmοusemοve="this.style.backgroundColor='#465e79'" οnmοuseοut="this.style.backgroundColor='#293846'" href="/toFunctionManagement">功能管理</a>
数据库中角色存储的权限字符串展示(varchar2类型):
3.5 最终测试结果
3.5.1 用户认证结果
未认证时,申请URL: /student, 被拦截进入登录界面。
3.5.2 用户授权结果
如图所示,左侧为未认证用户,没有任何权限,带有权限的功能都没有展示。(系统管理,用户管理,角色管理,功能管理)
右侧则为管理员用户,所有带权限的功能完全开放。
以上是关于SpringBoot实现基于shiro安全框架的,配合thymeleaf模板引擎的用户认证和授权的主要内容,如果未能解决你的问题,请参考以下文章
SpringBoot2.0 整合 Shiro 框架,实现用户权限管理
简单易用的Apache shiro框架,以及复杂完整的springboot security安全框架
SpringBoot+Shiro+Jwt实现登录认证——最干的干货