ITPub博客

首页 > 数据库 > 数据库开发技术 > Spring Boot集成Shiro快速入门Demo

Spring Boot集成Shiro快速入门Demo

原创 数据库开发技术 作者:hblog 时间:2024-05-17 08:33:45 0 删除 编辑

1.什么是shiro?

Shiro是一个功能强大、灵活的,开源的安全框架,主要可以帮助我们解决程序开发中认证和权限等问题。基于拦截器做的权限系统,权限控制的粒度有限,为了方便各种各样的常用的权限管理需求的实现,我们有必要使用比较好的安全框架。

早期Spring security 作为一个比较完善的安全框架比较火,但是Springsecurity学习成本比较高,于是就出现了shiro安全框架,学习成本降低了很多,而且基本的功能也比较完善。

Shiro的架构

  1. Subject:主题。被验证的对象,一般指的当前用户对象。但是不仅仅可以指当前用户对象,还可以是其他东西,线程等等。spring mvc中一个一个的用户的请求。

  2. SecurityManager:安全认证管理器。是shiro的核心,会在安全认证管理器中所做所有的认证操作。类似于之前Spring MVC中的前端控制器(DispacherServlet)。

  3. Realm:域的意思。负责访问安全认证数据。shiro框架并不存在安全认证数据,安全认证数据需要用户自己存储。shiro支持很多的Realm实现,也就是说安全认证数据我们可以放到数据库中,也可以放到文件中等等。可以把realm理解为以前web项目的dao层。

Shiro的功能

  1. Authentication:身份认证/登陆,验证用户是不是拥有相对应的身份,通常被称为用户“登录”;

  2. Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者粒度的验证某个用户对某个资源是否具有权限;

  3. Session Manager:会话管理,即用户登陆后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是Web环境的;

  4. Cryptographt:加密,保护数据,如密码加密存储到数据库,而不是明文存储;

  5. Web Support:Web支持,可以保护 Web 应用程序的安全;

  6. Caching:缓存,比如用户登陆后,其用户信息、拥有的角色/权限不必每次去查,这样提高效率;

  7. Concurrency:多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;

  8. Testing:支持单元测试和集成测试,确保代码和预想的一样安全;

  9. Run As:允许一个用户假装另一个用户(如果我们允许)的身份进行访问;

  10. Remember Me:记住我,这个是非常常见的功能,即一次登陆后,下次再来的话不用登陆了。

2.环境搭建

mysql database

docker run --name docker-mysql-5.7 -e MYSQL_ROOT_PASSWORD=123456 -p 3306:3306 -d mysql:5.7

 init data

create database demo;CREATE TABLE `sys_permission` (  `id` int(11) NOT NULL AUTO_INCREMENT,  `available` tinyint(4) DEFAULT NULL,  `name` varchar(100) DEFAULT NULL,  `parent_id` int(11) DEFAULT NULL,  `permission` varchar(100) DEFAULT NULL,  `resource_type` varchar(100) DEFAULT NULL,  `url` varchar(100) DEFAULT NULL,  `parent_ids` varchar(100) DEFAULT NULL,  PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
CREATE TABLE `sys_role` ( `id` int(11) NOT NULL AUTO_INCREMENT, `available` tinyint(4) DEFAULT NULL, `description` varchar(100) DEFAULT NULL, `role` varchar(100) DEFAULT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
CREATE TABLE `sys_role_permission` ( `permission_id` int(11) DEFAULT NULL, `role_id` int(11) DEFAULT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `sys_user_role` ( `uid` int(11) DEFAULT NULL, `role_id` int(11) DEFAULT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `user_info` ( `uid` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(100) CHARACTER SET latin1 DEFAULT NULL, `name` varchar(200) DEFAULT NULL, `password` varchar(100) CHARACTER SET latin1 DEFAULT NULL, `salt` varchar(100) CHARACTER SET latin1 DEFAULT NULL, `state` tinyint(4) DEFAULT NULL, PRIMARY KEY (`uid`)) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
INSERT INTO `user_info` (`uid`,`username`,`name`,`password`,`salt`,`state`) VALUES ('1', 'admin', '管理员', 'd3c59d25033dbf980d29554025c23a75', '8d78869f470951332959580424d4bf4f', 0);INSERT INTO `sys_role` (`id`,`available`,`description`,`role`) VALUES (1,0,'管理员','admin');INSERT INTO `sys_role` (`id`,`available`,`description`,`role`) VALUES (2,0,'VIP会员','vip');INSERT INTO `sys_role` (`id`,`available`,`description`,`role`) VALUES (3,1,'test','test');INSERT INTO `sys_permission` (`id`,`available`,`name`,`parent_id`,`parent_ids`,`permission`,`resource_type`,`url`) VALUES (1,0,'用户管理',0,'0/','userInfo:view','menu','userInfo/userList');INSERT INTO `sys_permission` (`id`,`available`,`name`,`parent_id`,`parent_ids`,`permission`,`resource_type`,`url`) VALUES (2,0,'用户添加',1,'0/1','userInfo:add','button','userInfo/userAdd');INSERT INTO `sys_permission` (`id`,`available`,`name`,`parent_id`,`parent_ids`,`permission`,`resource_type`,`url`) VALUES (3,0,'用户删除',1,'0/1','userInfo:del','button','userInfo/userDel');INSERT INTO `sys_user_role` (`role_id`,`uid`) VALUES (1,1);INSERT INTO `sys_role_permission` (`permission_id`,`role_id`) VALUES (1,1);INSERT INTO `sys_role_permission` (`permission_id`,`role_id`) VALUES (2,1);INSERT INTO `sys_role_permission` (`permission_id`,`role_id`) VALUES (3,2);

remark

msyql account:rootmysql password:123456

3.代码工程

实验目的:使用shiro控制访问权限

pom.xml

<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0"         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">    <parent>        <artifactId>springboot-demo</artifactId>        <groupId>com.et</groupId>        <version>1.0-SNAPSHOT</version>    </parent>    <modelVersion>4.0.0</modelVersion>
<artifactId>shiro</artifactId>
<properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <dependencies>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> </dependency> </dependencies></project>

shiro config

调用userservice读取用户信息

package com.et.shiro.config;
import com.et.shiro.entity.SysPermission;import com.et.shiro.entity.SysRole;import com.et.shiro.entity.UserInfo;import com.et.shiro.service.UserInfoService;import org.apache.shiro.authc.AuthenticationException;import org.apache.shiro.authc.AuthenticationInfo;import org.apache.shiro.authc.AuthenticationToken;import org.apache.shiro.authc.SimpleAuthenticationInfo;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.util.ByteSource;
import javax.annotation.Resource;
public class MyShiroRealm extends AuthorizingRealm { @Resource private UserInfoService userInfoService;
//check username and password @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("MyShiroRealm.doGetAuthenticationInfo()"); String username = (String)token.getPrincipal(); System.out.println(token.getCredentials()); //query user by username //in here ,you can cache some data for efficient UserInfo userInfo = userInfoService.findByUsername(username); System.out.println("----->>userInfo="+userInfo); if(userInfo == null){ return null; } SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo( userInfo, //username userInfo.getPassword(), //password ByteSource.Util.bytes(userInfo.getCredentialsSalt()),//salt=username+salt getName() //realm name ); return authenticationInfo; }
//authority controller @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { System.out.println("authority config-->MyShiroRealm.doGetAuthorizationInfo()"); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); UserInfo userInfo = (UserInfo)principals.getPrimaryPrincipal(); for(SysRole role:userInfo.getRoleList()){ authorizationInfo.addRole(role.getRole()); for(SysPermission p:role.getPermissions()){ authorizationInfo.addStringPermission(p.getPermission()); } } return authorizationInfo; }}

配置过滤链

package com.et.shiro.config;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;import org.apache.shiro.mgt.SecurityManager;import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;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 org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
import java.util.LinkedHashMap;import java.util.Map;import java.util.Properties;
@Configurationpublic class ShiroConfig { @Bean public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) { System.out.println("ShiroConfig.shirFilter()"); ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); // filter Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>(); // config filterChain filterChainDefinitionMap.put("/static/**", "anon"); // logout filterChainDefinitionMap.put("/logout", "logout"); // filterChainDefinitio,from up to down,so put /** into the last // authc:have a permission anon:anonymous access filterChainDefinitionMap.put("/**", "authc"); //default login shiroFilterFactoryBean.setLoginUrl("/login"); //success shiroFilterFactoryBean.setSuccessUrl("/index"); // Unauthorized; shiroFilterFactoryBean.setUnauthorizedUrl("/403"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; }
/** * 凭证匹配器 * (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了) * @return */
// 因为我们的密码是加过密的,所以,如果要Shiro验证用户身份的话,需要告诉它我们用的是md5加密的,并且是加密了两次。 // 同时我们在自己的Realm中也通过SimpleAuthenticationInfo返回了加密时使用的盐。 // 这样Shiro就能顺利的解密密码并验证用户名和密码是否正确了。 @Bean public HashedCredentialsMatcher hashedCredentialsMatcher(){ HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法; hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5(md5("")); return hashedCredentialsMatcher; }
@Bean public MyShiroRealm myShiroRealm(){ MyShiroRealm myShiroRealm = new MyShiroRealm(); myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());// 设置解密规则 return myShiroRealm; }
// SecurityManager是Shiro架构的核心,通过它来链接Realm和用户(文档中称之为Subject.) @Bean public SecurityManager securityManager(){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(myShiroRealm()); return securityManager; }
/** * 开启shiro aop注解支持. * 使用代理方式;所以需要开启代码支持; * @param securityManager * @return */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){ AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; }
@Bean(name="simpleMappingExceptionResolver") public SimpleMappingExceptionResolver createSimpleMappingExceptionResolver() { SimpleMappingExceptionResolver r = new SimpleMappingExceptionResolver(); Properties mappings = new Properties(); mappings.setProperty("DatabaseException", "databaseError");//数据库异常处理 mappings.setProperty("UnauthorizedException","403"); r.setExceptionMappings(mappings); // None by default r.setDefaultErrorView("error"); // No default r.setExceptionAttribute("ex"); // Default is "exception" //r.setWarnLogCategory("example.MvcLogger"); // No default return r; }}

controller

package com.et.shiro.controller;
import org.apache.shiro.authc.IncorrectCredentialsException;import org.apache.shiro.authc.UnknownAccountException;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;import java.util.Map;
@Controllerpublic class HomeController {
@RequestMapping({"/","/index"}) public String index(){ return"/index"; }
@RequestMapping("/login") public String login(HttpServletRequest request, Map<String, Object> map) throws Exception{ System.out.println("HomeController.login()"); // shiroLoginFailure String exception = (String) request.getAttribute("shiroLoginFailure"); System.out.println("exception=" + exception); String msg = ""; if (exception != null) { if (UnknownAccountException.class.getName().equals(exception)) { System.out.println(" -- > account isn't exist"); msg = " -- > account isn't exist"; } else if (IncorrectCredentialsException.class.getName().equals(exception)) { System.out.println(" -- > password isn't right"); msg = " -- > password isn't right"; } else { msg = "else >> "+exception; System.out.println("else -- >" + exception); } } map.put("msg", msg); return "/login"; }
@RequestMapping("/403") public String unauthorizedRole(){ System.out.println("------unauthorizedRole-------"); return "403"; }}

设置不同链接访问权限

package com.et.shiro.controller;
import org.apache.shiro.authz.annotation.RequiresPermissions;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;
@Controller@RequestMapping("/userInfo")public class UserInfoController {
/** * userList * @return */ @RequestMapping("/userList") @RequiresPermissions("userInfo:view") public String userInfo(){ return "userInfo"; }
/** * userAdd * @return */ @RequestMapping("/userAdd") @RequiresPermissions("userInfo:add") public String userInfoAdd(){ return "userInfoAdd"; }
/** * userDel * @return */ @RequestMapping("/userDel") @RequiresPermissions("userInfo:del") public String userDel(){ return "userInfoDel"; }}

jpa读取数据库数据

因为在entity里面设置及联关系,所以jpa会自动加载关联数据出来

@ManyToMany(fetch= FetchType.EAGER)@JoinTable(name = "SysUserRole", joinColumns = { @JoinColumn(name = "uid") }, inverseJoinColumns ={@JoinColumn(name = "roleId") })private List<SysRole> roleList;

继承公共crud操作类

package com.et.shiro.dao;
import com.et.shiro.entity.UserInfo;import org.springframework.data.repository.CrudRepository;
public interface UserInfoDao extends CrudRepository<UserInfo,Long> {
public UserInfo findByUsername(String username);}

teamplate

在resource目录下,你可以在代码仓库看到,这里不一个个列举出来

application.yaml

server:  port: 8088spring:  datasource:    url: jdbc:mysql://localhost:3306/demo?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC    username: root    password: 123456    #schema: database/import.sql    #sql-script-encoding: utf-8    driver-class-name: com.mysql.cj.jdbc.Driver
jpa: database: mysql show-sql: true hibernate: ddl-auto: update properties: hibernate: dialect: org.hibernate.dialect.MySQL5Dialect
thymeleaf: cache: false mode: HTML

以上只是一些关键代码,所有代码请参见下面代码仓库


代码仓库

  • https://github.com/Harries/springboot-demo

4.测试

启动Spring Boot应用

测试未登录访问

访问http://localhost:8088/userInfo/userList页面,由于没有登录就会跳转到http://localhost:8088/login页面

 登陆后访问

登录后,能看首页信息

测试不同用户登陆

访问http://127.0.0.1:8088/userInfo/userDel显示403没有权限,因为需要@RequiresPermissions("userInfo:del"),而admin没有给它设置相应的权限


5.参考引用

  • https://github.com/ChuaWi/SpringBoot-Shiro?tab=readme-ov-file

  • http://www.liuhaihua.cn/archives/710553.html

原文作者:HBLOG;如有侵权,请联系管理员删除。

请登录后发表评论 登录
全部评论

注册时间:2022-05-04

  • 博文量
    210
  • 访问量
    111756