1 shiro
subject: 用户主体
SecurityManager:安全管理器
Realm:Shiro连接数据的桥梁
2 Simple to use
2.1 Business description
Business requirement: Realize login authentication and resource access authorization
Technical selection:
spring boot:2.3.0.RELEASE
jdk:1.8
shiro:1.5.3
mysql:5.5.27
2.2 shiro integrated boot
2.2.1 Establish project shiro
1 Create a new spring project, choose lombok as the development tool
2 Modify pom, increase shiro dependency org.apache.shiro.shiro-spring.version:1.5.3
3 Add a specific realm class that implements AuthorizingRealm
package cn.ithzp.shiro.config;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
@Slf4j
public class UserRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
log.info("执行授权逻辑");
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
log.info("执行认证逻辑");
return null;
}
}
4 Add shiro configuration class
package cn.ithzp.shiro.config;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import java.util.LinkedHashMap;
import java.util.Map;
@Slf4j
@Configuration
public class ShiroConfig {
@Bean("userRealm")
public UserRealm getRealm() {
log.info("获取继承了AuthorizingRealm的具体realm对象");
return new UserRealm();
}
@Bean("securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(
@Qualifier("userRealm") UserRealm userRealm
) {
log.info("获取关联具体realm的securityManager安全管理器");
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
defaultWebSecurityManager.setRealm(userRealm);
return defaultWebSecurityManager;
}
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(
@Qualifier("securityManager") DefaultWebSecurityManager securityManager
) {
log.info("获取shiro内置过滤器");
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
/**
* shiro内置过滤器包含anon/authc/user/perms/role
* anon:无需认证
* authc:必须认证
* user:如果使用rememberme可以直接访问
* perms:必须有资源权限才可以访问
* role:必须有角色权限才可以放
*/
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/*", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
shiroFilterFactoryBean.setSecurityManager(securityManager);
return shiroFilterFactoryBean;
}
}
2.2.2 Simple demonstration of authc
1 Modify the pom file, add web and thymeleaf dependencies spring-boot-starter-web和spring-boot-starter-thymeleaf
2 Increase the permission demo class CommonController
package cn.ithzp.shiro.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@Slf4j
public class CommonController {
@RequestMapping("/bridge")
public String bridge(Model model) {
log.info("进入权限跳转页面");
model.addAttribute("name", "权限跳转页面");
return "bridge";
}
}
3 Add front-end page resources/templates/bridge.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>权限跳转页面</title>
</head>
<body>
<h3 th:text="${name}"></h3>
<hr/>
进入用户添加功能:<a href="add">用户添加</a></br>
进入用户更新功能:<a href="update">用户更新</a>
</body>
</html>
访问http://localhost:8080/bridge
- 结果1:ShiroConfig中取消注解@Configuration,可以正常访问
- 结果2:ShiroConfig中增加注解@Configuration,页面404,跳转到http://localhost:8080/login.jsp
2.2.3 Writing login logic
1 Modify ShiroConfig, increase login permission, increase login page after access failure
// 需要放到authc的前面
filterMap.put("/login", "anon");
filterMap.put("/*", "authc");
shiroFilterFactoryBean.setLoginUrl("/toLogin");
2 Modify CommonController to add a method of jumping to the login page and a method of login verification
@RequestMapping("/toLogin")
public String toLogin() {
log.info("跳转登录页");
return "/login";
}
@RequestMapping("/login")
public String login(String userName, String passwd, Model model) {
log.info("进入登录操作,参数userName={},passwd={}", userName, passwd);
// 1 获取用户主体
Subject subject = SecurityUtils.getSubject();
// 2 封装用户数据
UsernamePasswordToken token = new UsernamePasswordToken(userName, passwd);
// 3 执行登录
try {
subject.login(token);
log.info("登录成功");
return "redirect:bridge";
} catch (UnknownAccountException u) {
model.addAttribute("msg", "用户不存在");
return "login";
} catch (IncorrectCredentialsException i) {
log.info("密码错误");
model.addAttribute("msg", "密码错误");
return "login";
}
}
3 Modify UserRealm, modify login authentication
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
log.info("执行认证逻辑");
String userName = "a";
String password = "b";
// 1 用户名验证
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
if (!StringUtils.equals(userName, token.getUsername())) {
log.info("用户不存在");
// 会抛出UnknownAccountException
return null;
}
// 2 密码验证
return new SimpleAuthenticationInfo("", password, "");
}
4 Increase the front-end page resources/templates/login.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>登录页面</title>
</head>
<h3>登录</h3>
<h3 th:text="${msg}" style="color:red"></h3>
<body>
<form method="post" action="/login">
用户名:<input type="text" name="userName" /></br>
密码:<input type="password" name="passwd"></br>
<input type="submit" value="登录"/>
</form>
</body>
</html>
1 浏览器访问http://localhost:8080/bridge
- 结果跳转到http://localhost:8080/toLogin
2 输入错误用户名提示用户不存在
3 输入错误密码提示密码错误
4 输入正确用户名密码,页面重定向到http://localhost:8080/bridge
2.2.4 Write authorization logic
1 Modify ShiroConfig, increase resource permissions, and set unauthorized jump pages
filterMap.put("/add", "perms[user:add]");
filterMap.put("/update", "perms[user:update]");
filterMap.put("/*", "authc");
shiroFilterFactoryBean.setUnauthorizedUrl("/unauth");
2 Modify CommonControler, add method add/update/unauth
@RequestMapping("/add")
public String add() {
log.info("执行用户添加");
return "/user/add";
}
@RequestMapping("/update")
public String update() {
log.info("执行用户更新");
return "/user/update";
}
@RequestMapping("/unauth")
@ResponseBody
public String unauth() {
log.info("未授权");
return "亲,您还未被授权";
}
3 Add front-end pages /templates/user/add.html and update.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>用户添加页面</title>
</head>
<body>
用户添加
</body>
</html>
4 Modify UserRealm and add authorization
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
log.info("执行授权逻辑");
// 对资源进行授权
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// info.addStringPermission("user:add");
info.addStringPermission("user:update");
return info;
}
1 访问http://localhost:8080/toLogin后执行登录操作
- 结果会跳转到http://localhost:8080/bridge
2 点击页面中用户添加,跳转http://localhost:8080/unauth,提示未授权
3 点击页面中用户更新,跳转http://localhost:8080/update,提示更新
2.3 Shiro dynamic data (boot integrated mybatis)
2.3.1 Basic construction
1 Modify pom to introduce mybatis
<com.alibaba.druid.version>1.1.22</com.alibaba.druid.version>
<mysql-connector-java.version>5.1.42</mysql-connector-java.version>
<mybatis-spring-boot-starter>1.1.1</mybatis-spring-boot-starter>
2 Add configuration/resources/application.properties
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/shiro?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
mybatis.typeAliasesPackage=cn.ithzp.shiro.domain
mybatis.mapper-locations=classpath:mapper/*.xml
# 打印sql日志
loggin.level.cn.ithzp.shiro.mapper=debug
3 Create table/add entity class
package cn.ithzp.shiro.domain;
import lombok.Data;
@Data
public class UserDomain {
private int id;
private String userName;
private String passwd;
private String perms;
}
4 Increase the persistence layer cn.ithzp.shiro.mapper.UserMapper
package cn.ithzp.shiro.mapper;
import cn.ithzp.shiro.domain.UserDomain;
public interface UserMapper {
UserDomain findByUserName(String userName);
}
5 Add xml mapping src/main/resources/mapper/UserMapper.xml
<?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">
<mapper namespace="cn.ithzp.shiro.mapper.UserMapper">
<select id="findByUserName" parameterType="string" resultType="UserDomain">
select id, user_name as userName, passwd from user where user_name=#{value}
</select>
</mapper>
6 Add business processing service and serviceImpl
package cn.ithzp.shiro.service.impl;
import cn.ithzp.shiro.domain.UserDomain;
import cn.ithzp.shiro.mapper.UserMapper;
import cn.ithzp.shiro.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public UserDomain findByUserName(String userName) {
return userMapper.findByUserName(userName);
}
}
7 Startup class to increase mapper scanning
@SpringBootApplication
@MapperScan("cn.ithzp.shiro.mapper")
2.3.2 Use database login authentication
1 Modify the login verification method of UserRealm
// 1 用户名验证
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
UserDomain dbUser = userService.findByUserName(token.getUsername());
log.info("数据库用户信息为:{}", dbUser);
if (dbUser == null) {
log.info("用户不存在");
// 会抛出UnknownAccountException
return null;
}
// 2 密码验证
return new SimpleAuthenticationInfo("", dbUser.getPasswd(), "");
访问http://localhost:8080/toLogin后执行登录操作,输入数据库用户名和密码可以正常跳转
2.3.3 Using a database for authorization operations
1 Modify UserRealm, modify the password verification after login authentication, and pass the object to the authorization method
// 2 密码验证
return new SimpleAuthenticationInfo(dbUser , dbUser.getPasswd(), "");
2 Add query authorization method in service/impl/mapper/xml
<select id="findById" parameterType="string" resultType="UserDomain">
select id, user_name as userName, passwd, perms from user where id=${value}
</select>
3 Modify UserRealm, modify the authorization method, and obtain the permissions dynamically from the database
// 对资源进行授权
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 获取subject对象
Subject subject = SecurityUtils.getSubject();
UserDomain loginUser = (UserDomain) subject.getPrincipal();
UserDomain dbUser = userService.findById(loginUser.getId());
info.addStringPermission(dbUser.getPerms());
return info;
1 访问http://localhost:8080/toLogin后采用jack登录,
- 跳转http://localhost:8080/bridge
2 点击“用户添加”,跳转到http://localhost:8080/add,提示用户添加
3 点击“用户更新”,跳转到http://localhost:8080/unauth,提示未授权
2.4 thymeleaf matching shiro
1 Modify pom, increase dependency
<com.github.theborakompanioni.thymeleaf-extras-shiro>2.0.0</com.github.theborakompanioni.thymeleaf-extras-shiro>
2 Modify ShiroConfig, add configuration
/**
* 配置ShiroDialect,用于thymeleaf和shiro标签配合使用
*/
@Bean
public ShiroDialect getShiroDialect() {
return new ShiroDialect();
}
3 Modify Bridge.html and increase shiro permissions
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>权限跳转页面</title>
</head>
<body>
<h3 th:text="${name}"></h3>
<hr/>
<div shiro:hasPermission="user:add">
进入用户添加功能:<a href="add">用户添加</a></br>
</div>
<div shiro:hasPermission="user:update">
进入用户更新功能:<a href="update">用户更新</a>
</div>
</body>
</html>
访问登录页http://localhost:8080/toLogin
- 采用jack登录,只能查看用户添加
- 采用tom登录,只能查看用户更新