Spring Boot Integration Shiro Quick Start Demo
1. What is SHIRO?
Shiro is a powerful, flexible, open-source security framework that can help us solve problems such as authentication and permissions in program development. In order to facilitate the implementation of various commonly used permission management requirements, it is necessary to use a better security framework.
In the early days, Spring security was popular as a relatively complete security framework, but the learning cost of Springsecurity was relatively high, so the Shiro security framework appeared, the learning cost was reduced a lot, and the basic functions were relatively perfect.
Shiro’s architecture

Subject
:Topic. The object to be authenticated generally refers to the current user object. But it can not only refer to the current user object, but also other things, threads, and so on. Requests from users one by one in Spring MVC.SecurityManager
: Security Authentication Manager. It is the core of Shiro, and all authentication operations will be done in the security authentication manager. Similar to the front-end controller (DispacherServlet) in Spring MVC.Realm
: The meaning of the domain. Responsible for accessing secure authentication data. There is no security authentication data in the Shiro framework, and the security authentication data needs to be stored by the user. shiro supports a lot of Realm implementations, which means that we can put security authentication data in a database, a file, and so on. Realm can be thought of as the DAO layer of previous web projects.
Shiro’s features
- Authentication: Authentication/login, which verifies whether the user has the corresponding identity, which is usually referred to as user “login”;
- Authorization: authorization, that is, permission verification, verifies whether an authenticated user has a certain permission. That is, to determine whether a user can do something, such as verifying whether a user has a certain role. or at a granular level, verify whether a user has permissions on a resource;
- Session Manager: Session management, that is, after the user logs in, it is a session, and all its information is in the session before exiting; The session can be a normal JavaSE environment or a web environment;
- Cryptographt: Encrypted, protected data, such as cryptographic encrypted storage to the database instead of plaintext storage;
- Web Support: Web support, which can protect the security of web applications;
- Caching: Caching, for example, after a user logs in, its user information and role/permissions do not need to be checked every time, which improves efficiency;
- Concurrency: concurrency verification for multi-threaded applications, that is, if another thread is opened in one thread, the permission can be automatically propagated;
- Testing: Support unit testing and integration testing to ensure that the code is as secure as expected;
- Run As: allows one user to pretend to be another user (if we allow) for access;
- Remember Me: Remember me, this is a very common feature, that is, after logging in once, you don’t need to log in next time.
2. Environment construction
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', 'admin', 'd3c59d25033dbf980d29554025c23a75', '8d78869f470951332959580424d4bf4f', 0);
INSERT INTO `sys_role` (`id`,`available`,`description`,`role`) VALUES (1,0,'admin','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,'manager',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,'add',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,'del',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:root
mysql password:123456
3. Code engineering
Purpose: Use Shiro to control access rights
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 configuration
Call userservice to read user information
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;
}
}
Configure the filter chain
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;
@Configuration
public 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;
}
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(){
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5");
hashedCredentialsMatcher.setHashIterations(2);
return hashedCredentialsMatcher;
}
@Bean
public MyShiroRealm myShiroRealm(){
MyShiroRealm myShiroRealm = new MyShiroRealm();
myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return myShiroRealm;
}
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myShiroRealm());
return securityManager;
}
/**
* @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;
@Controller
public 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";
}
}
Set access to different links
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 reads database data
Because the association relationship is set in the entity, JPA will automatically load the association data
@ManyToMany(fetch= FetchType.EAGER)
@JoinTable(name = "SysUserRole", joinColumns = { @JoinColumn(name = "uid") }, inverseJoinColumns ={@JoinColumn(name = "roleId") })
private List<SysRole> roleList;
Inherit from the public CRUD operation class
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);
}
template
In the resource directory, you can see it in the code repository, and I won’t list them all here
application.yaml
server:
port: 8088
spring:
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
The above are just some of the key codes, all of which can be found in the repositories below
Code repositories
4. Testing
Start the Spring Boot application
Test not logged in access
visithttp://localhost:8088/userInfo/userList
page, you will be redirected to it if you are not logged inhttp://localhost:8088/login
Page
Access after logging in
After logging in, you can see top page information
Test different user logins
visithttp://127.0.0.1:8088/userInfo/userDel
Show 403 no permissions because needed@RequiresPermissions("userInfo:del"),but admin have no this permission