引自:30分钟了解Springboot整合Shiro

前言:06年7月的某日,不才创作了一篇题为《30分钟学会如何使用Shiro》的文章。不在意之间居然斩获了22万的阅读量,许多人因此加了我的联系方式咨询源码工程,只可惜当时并没有专门保留。2年后的今天在机缘巧合之下,我又重拾此话题。希望能带给小伙伴们在Springboot下如何使用Shiro,当然若各位感兴趣我还希望之后能创作一些与它有关的更加深入的知识。作为一个知识分享型博主,我希望能够帮助大家尽快上手。因此我尽可能去除了与整合无关的干扰因素,方便大家只要按照文章的思路就一定能有所收获。

项目结构截图:

项目在结构上没有任何特殊之处,基本就是MVC的传统结构重点需要关注的是3个Entity类、2个Controller类和1个Config类。

首先,提供pom的完整文档结构:

<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">
<modelVersion>4.0.0</modelVersion> <groupId>com.learnhow.springboot</groupId>
<artifactId>web</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging> <name>web</name>
<url>http://maven.apache.org</url> <parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.4.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties> <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</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-devtools</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork>
</configuration>
</plugin>
</plugins>
</build>
</project>

其次,创建数据库和表结构。由于我们采用jpa作为数据库持久层框架,因此我们将建表的任务交给框架自动完成,我们只需要在entity中写清楚对应关系即可。

在yml文件中配置自动建表,并且写好entity后,执行项目,jpa会执行建表语句。

CREATE DATABASE enceladus;  // enceladus是数据库的名称

application.yml

server:
port: 8088
spring:
application:
name: shiro
datasource:
url: jdbc:mysql://192.168.31.37:3306/enceladus
username: root
password: 12345678
driver-class-name: com.mysql.jdbc.Driver
jpa:
database: mysql
showSql: true
hibernate:
ddlAuto: update
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL5Dialect
format_sql: true

最基础的Shiro配置至少需要三张主表分别代表用户(user)、角色(role)、权限(permission),用户和角色,角色与权限之间都是ManyToMany的对应关系,不熟悉实体对应关系的小伙伴可以先去熟悉一下Hibernate。

User.java

package com.learnhow.springboot.web.entity;

import java.io.Serializable;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table; /**
* 用户表
*/
@Entity
@Table(name = "user_t")
public class User implements Serializable {
private static final long serialVersionUID = -3320971805590503443L;
@Id
@GeneratedValue
private long id;
private String username;
private String password;
private String salt;
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "user_role_t", joinColumns = { @JoinColumn(name = "uid") }, inverseJoinColumns = {
@JoinColumn(name = "rid") })
private List<SysRole> roles; public long getId() {
return id;
} public void setId(long id) {
this.id = id;
} public String getUsername() {
return username;
} public void setUsername(String username) {
this.username = username;
} public String getPassword() {
return password;
} public void setPassword(String password) {
this.password = password;
} public String getSalt() {
return salt;
} public void setSalt(String salt) {
this.salt = salt;
} public List<SysRole> getRoles() {
return roles;
} public void setRoles(List<SysRole> roles) {
this.roles = roles;
} public String getCredentialsSalt() {
return username + salt + salt;
} @Override
public String toString() {
return "User [id=" + id + ", username=" + username + "]";
} }

User

SysRole.java

package com.learnhow.springboot.web.entity;

import java.io.Serializable;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table; /**
* 角色表
*/
@Entity
@Table(name = "role_t")
public class SysRole implements Serializable {
private static final long serialVersionUID = -8687790154329829056L;
@Id
@GeneratedValue
private Integer id;
private String role;
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "role_permission_t", joinColumns = { @JoinColumn(name = "rid") }, inverseJoinColumns = {
@JoinColumn(name = "pid") })
private List<SysPermission> permissions;
@ManyToMany
@JoinTable(name = "user_role_t", joinColumns = { @JoinColumn(name = "rid") }, inverseJoinColumns = {
@JoinColumn(name = "uid") })
private List<User> users; public Integer getId() {
return id;
} public void setId(Integer id) {
this.id = id;
} public String getRole() {
return role;
} public void setRole(String role) {
this.role = role;
} public List<SysPermission> getPermissions() {
return permissions;
} public void setPermissions(List<SysPermission> permissions) {
this.permissions = permissions;
} public List<User> getUsers() {
return users;
} public void setUsers(List<User> users) {
this.users = users;
} }

UserRole

SysPermission.java

package com.learnhow.springboot.web.entity;

import java.io.Serializable;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table; /**
* 权限表
*/
@Entity
@Table(name = "permission_t")
public class SysPermission implements Serializable {
private static final long serialVersionUID = 353629772108330570L;
@Id
@GeneratedValue
private Integer id;
private String name;
@ManyToMany
@JoinTable(name = "role_permission_t", joinColumns = { @JoinColumn(name = "pid") }, inverseJoinColumns = {
@JoinColumn(name = "rid") })
private List<SysRole> roles; public Integer getId() {
return id;
} public void setId(Integer id) {
this.id = id;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public List<SysRole> getRoles() {
return roles;
} public void setRoles(List<SysRole> roles) {
this.roles = roles;
}
}

SysPermission

在注明对应关系以后,jpa会帮助我们创建3张实体表和2张中间表:

最后我们还需要初始化一些基础数据:

INSERT INTO `permission_t` VALUES (1, 'Retrieve');
INSERT INTO `permission_t` VALUES (2, 'Create');
INSERT INTO `permission_t` VALUES (3, 'Update');
INSERT INTO `permission_t` VALUES (4, 'Delete');
INSERT INTO `role_t` VALUES (1, 'guest');
INSERT INTO `role_t` VALUES (2, 'user');
INSERT INTO `role_t` VALUES (3, 'admin');
INSERT INTO `role_permission_t` VALUES (1, 1);
INSERT INTO `role_permission_t` VALUES (1, 2);
INSERT INTO `role_permission_t` VALUES (2, 2);
INSERT INTO `role_permission_t` VALUES (3, 2);
INSERT INTO `role_permission_t` VALUES (1, 3);
INSERT INTO `role_permission_t` VALUES (2, 3);
INSERT INTO `role_permission_t` VALUES (3, 3);
INSERT INTO `role_permission_t` VALUES (4, 3);

至此,前期的准备工作已经完成。下面为了让Shiro能够在项目中生效我们需要通过代码的方式提供配置信息。Shiro的安全管理提供了两个层面的控制:(1)用户认证:需要用户通过登陆证明你是你自己。(2)权限控制:在证明了你是你自己的基础上系统为当前用户赋予权限。后者我们已经在数据库中完成了大部分配置。

用户认证的常规手段就是登陆认证,在目前的情况下我们认为只有用户自己知道登陆密码。不过Shiro为我们做的更多,它还提供了一套能够很方便我们使用的密码散列算法。因为普通的散列技巧可以很容易的通过暴力手段破解,我们可以在散列的过程中加入一定的算法复杂度(增加散列次数与Salt)从而解决这样的问题。

import org.apache.shiro.crypto.RandomNumberGenerator;
import org.apache.shiro.crypto.SecureRandomNumberGenerator;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.util.ByteSource; import com.learnhow.springboot.web.entity.User; public class PasswordHelper {
private RandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator();
public static final String ALGORITHM_NAME = "md5"; // 基础散列算法
public static final int HASH_ITERATIONS = 2; // 自定义散列次数 public void encryptPassword(User user) {
// 随机字符串作为salt因子,实际参与运算的salt我们还引入其它干扰因子
user.setSalt(randomNumberGenerator.nextBytes().toHex());
String newPassword = new SimpleHash(ALGORITHM_NAME, user.getPassword(),
ByteSource.Util.bytes(user.getCredentialsSalt()), HASH_ITERATIONS).toHex();
user.setPassword(newPassword);
}
}

这个类帮助我们解决用户注册的密码散列问题,当然我们还需要使用同样的算法来保证在登陆的时候密码能够被散列成相同的字符串。如果两次散列的结果不同系统就无法完成密码比对,因此在计算散列因子的时候我们不能引入变量,例如我们可以将username作为salt因子加入散列算法,但是不能选择password或datetime,具体原因各位请手动测试。

另外为了帮助Shiro能够正确为当前登陆用户做认证和赋权,我们需要实现自定义的Realm。具体来说就是实现doGetAuthenticationInfo和doGetAuthorizationInfo,这两个方法前者负责登陆认证后者负责提供一个权限信息。

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 org.springframework.beans.factory.annotation.Autowired; import com.learnhow.springboot.web.entity.SysPermission;
import com.learnhow.springboot.web.entity.SysRole;
import com.learnhow.springboot.web.entity.User;
import com.learnhow.springboot.web.service.UserService; public class EnceladusShiroRealm extends AuthorizingRealm {
@Autowired
private UserService userService; @Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
String username = (String) principals.getPrimaryPrincipal(); User user = userService.findUserByName(username); for (SysRole role : user.getRoles()) {
authorizationInfo.addRole(role.getRole());
for (SysPermission permission : role.getPermissions()) {
authorizationInfo.addStringPermission(permission.getName());
}
}
return authorizationInfo;
} @Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String) token.getPrincipal();
User user = userService.findUserByName(username); if (user == null) {
return null;
}
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(),
ByteSource.Util.bytes(user.getCredentialsSalt()), getName());
return authenticationInfo;
} }

还记得前面我们说过,认证的时候我们需要提供相同的散列算法吗?可是在上面的代码里,我们并未提供。那么Shiro是怎么做的呢?AuthorizingRealm是一个抽象类,我们会在另外的配置文件里向它提供基础算法与散列次数这两个变量。

import java.util.HashMap;
import java.util.Map; import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
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; @Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager); Map<String, String> filterChainDefinitionMap = new HashMap<String, String>();
shiroFilterFactoryBean.setLoginUrl("/login");
shiroFilterFactoryBean.setUnauthorizedUrl("/unauthc");
shiroFilterFactoryBean.setSuccessUrl("/home/index"); filterChainDefinitionMap.put("/*", "anon");
filterChainDefinitionMap.put("/authc/index", "authc");
filterChainDefinitionMap.put("/authc/admin", "roles[admin]");
filterChainDefinitionMap.put("/authc/renewable", "perms[Create,Update]");
filterChainDefinitionMap.put("/authc/removable", "perms[Delete]");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
} @Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName(PasswordHelper.ALGORITHM_NAME); // 散列算法
hashedCredentialsMatcher.setHashIterations(PasswordHelper.HASH_ITERATIONS); // 散列次数
return hashedCredentialsMatcher;
} @Bean
public EnceladusShiroRealm shiroRealm() {
EnceladusShiroRealm shiroRealm = new EnceladusShiroRealm();
shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher()); // 原来在这里
return shiroRealm;
} @Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(shiroRealm());
return securityManager;
} @Bean
public PasswordHelper passwordHelper() {
return new PasswordHelper();
}
}

接下来,我们将目光集中到上文的shirFilter方法中。Shiro通过一系列filter来控制访问权限,并在它的内部为我们预先定义了多个过滤器,我们可以直接通过字符串配置这些过滤器。

常用的过滤器如下:

authc:所有已登陆用户可访问

roles:有指定角色的用户可访问,通过[ ]指定具体角色,这里的角色名称与数据库中配置一致

perms:有指定权限的用户可访问,通过[ ]指定具体权限,这里的权限名称与数据库中配置一致

anon:所有用户可访问,通常作为指定页面的静态资源时使用

为了测试方便我们不引入页面配置直接通过rest方式访问

不受权限控制访问的地址

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import com.learnhow.springboot.web.PasswordHelper;
import com.learnhow.springboot.web.entity.User;
import com.learnhow.springboot.web.service.UserService; @RestController
@RequestMapping
public class HomeController {
@Autowired
private UserService userService;
@Autowired
private PasswordHelper passwordHelper; @GetMapping("login")
public Object login() {
return "Here is Login page";
} @GetMapping("unauthc")
public Object unauthc() {
return "Here is Unauthc page";
} @GetMapping("doLogin")
public Object doLogin(@RequestParam String username, @RequestParam String password) {
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
Subject subject = SecurityUtils.getSubject();
try {
subject.login(token);
} catch (IncorrectCredentialsException ice) {
return "password error!";
} catch (UnknownAccountException uae) {
return "username error!";
} User user = userService.findUserByName(username);
subject.getSession().setAttribute("user", user);
return "SUCCESS";
} @GetMapping("register")
public Object register(@RequestParam String username, @RequestParam String password) {
User user = new User();
user.setUsername(username);
user.setPassword(password);
passwordHelper.encryptPassword(user); userService.saveUser(user);
return "SUCCESS";
}
}

需要指定权限可以访问的地址

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import com.learnhow.springboot.web.entity.User; @RestController
@RequestMapping("authc")
public class AuthcController { @GetMapping("index")
public Object index() {
Subject subject = SecurityUtils.getSubject();
User user = (User) subject.getSession().getAttribute("user");
return user.toString();
} @GetMapping("admin")
public Object admin() {
return "Welcome Admin";
} // delete
@GetMapping("removable")
public Object removable() {
return "removable";
} // insert & update
@GetMapping("renewable")
public Object renewable() {
return "renewable";
}
}

详情请参考完整代码:

https://gitee.com/jingxin168/jingxin.git

上手调试:注册用户

转:30分钟了解Springboot整合Shiro的更多相关文章

  1. 30分钟了解Springboot整合Shiro

    项目结构截图: 项目在结构上没有任何特殊之处,基本就是MVC的传统结构重点需要关注的是3个Entity类.2个Controller类和1个Config类. 首先,提供pom的完整文档结构: <p ...

  2. 30分钟学会如何使用Shiro

    本篇内容大多总结自张开涛的<跟我学Shiro>原文地址:http://jinnianshilongnian.iteye.com/blog/2018936 我并没有全部看完,只是选择了一部分 ...

  3. SpringBoot整合Shiro实现权限控制,验证码

    本文介绍 SpringBoot 整合 shiro,相对于 Spring Security 而言,shiro 更加简单,没有那么复杂. 目前我的需求是一个博客系统,有用户和管理员两种角色.一个用户可能有 ...

  4. SpringBoot整合Shiro权限框架实战

    什么是ACL和RBAC ACL Access Control list:访问控制列表 优点:简单易用,开发便捷 缺点:用户和权限直接挂钩,导致在授予时的复杂性,比较分散,不便于管理 例子:常见的文件系 ...

  5. SpringBoot系列十二:SpringBoot整合 Shiro

    声明:本文来源于MLDN培训视频的课堂笔记,写在这里只是为了方便查阅. 1.概念:SpringBoot 整合 Shiro 2.具体内容 Shiro 是现在最为流行的权限认证开发框架,与它起名的只有最初 ...

  6. SpringBoot 整合 Shiro 密码登录与邮件验证码登录(多 Realm 认证)

    导入依赖(pom.xml)  <!--整合Shiro安全框架--> <dependency> <groupId>org.apache.shiro</group ...

  7. 补习系列(6)- springboot 整合 shiro 一指禅

    目标 了解ApacheShiro是什么,能做什么: 通过QuickStart 代码领会 Shiro的关键概念: 能基于SpringBoot 整合Shiro 实现URL安全访问: 掌握基于注解的方法,以 ...

  8. SpringBoot整合Shiro实现基于角色的权限访问控制(RBAC)系统简单设计从零搭建

    SpringBoot整合Shiro实现基于角色的权限访问控制(RBAC)系统简单设计从零搭建 技术栈 : SpringBoot + shiro + jpa + freemark ,因为篇幅原因,这里只 ...

  9. springboot整合Shiro功能案例

    Shiro 核心功能案例讲解 基于SpringBoot 有源码 从实战中学习Shiro的用法.本章使用SpringBoot快速搭建项目.整合SiteMesh框架布局页面.整合Shiro框架实现用身份认 ...

随机推荐

  1. python的异常处理和模块发布安装

    1.完整的异常处理 异常处理能够保证程序出错是也能够完整运行,不会应为bug而停止运行,这里介绍下获取异常的完整格式 try: num = int(input("输入整数:")) ...

  2. AppDomain配置和卸载

    AppDomain 1.配置AppDomain 使用AppDomainSetup类为新应用程序域提供带有配置信息的公共语言运行时.创建自己的应用程序域时,最重要的ApplicationBase(它是定 ...

  3. win10系统 WMI Provider Host cpu 占用过高

    今天上班,发现系统有点卡,QQ总是没响应. 打开任务管理器,发现有一个程序- WMI Provider Host 占用cpu竟然高达80% 然后通过事件查看器发现是一个pid为9832的程序造成的 然 ...

  4. Linux下XAMPP的部署实战

    上传源码文件 rz -be 下载xampp安装包wget http://sourceforge.net/projects/xampp/files/XAMPP%20Linux/5.5.28/xampp- ...

  5. Sql的一些常规判断

    sql server中如何判断表或者数据库的存在,但在实际使用中,需判断Status状态位:其中某些状态位可由用户使用 sp_dboption(read only.dbo use only.singl ...

  6. AngularJs学习笔记--Understanding the Model Component

    原版地址:http://docs.angularjs.org/guide/dev_guide.mvc.understanding_model 在angular文档讨论的上下文中,术语“model”可以 ...

  7. C#导入PFX和Cer证书的工具类

    代码: public class CertificationHelper { public static bool importPFX(string certPath, string certPass ...

  8. commons.pool2 对象池的使用

    commons.pool2 对象池的使用 ? 1 2 3 4 5 <dependency>     <groupId>org.apache.commons</groupI ...

  9. JAVA中commons-collections-3.2.1.jar包是干什么用的?

    类似C++中的Boost库,对Java容器类型和算法的补充

  10. BZOJ2662:[BJWC2012]冻结(分层图最短路)

    Description “我要成为魔法少女!”     “那么,以灵魂为代价,你希望得到什么?” “我要将有关魔法和奇迹的一切,封印于卡片之中„„”        在这个愿望被实现以后的世界里,人们享 ...