0. Gitee项目地址
1. Shiro简介
Apache Shiro是一款主流的 Java 安全框架,不依赖任何容器,可以运行在 Java SE 和 Java EE 项目中,它的主要作用是对访问系统的用户进行身份认证、授权、会话管理、加密等操作。
Apache Shiro | Simple. Java. Security.
2. Shiro核心组件
-
UsernamePasswordToken:
Shiro 用来封装用户登录信息,使用用户的登录信息来创建令牌 Token
-
SecurityManager:
Shiro 的核心部分,负责安全认证和授权
-
Subject:
Shiro 的一个抽象概念,包含了用户信息
-
Realm:
开发者自定义的模块,根据项目的需求,验证和授权的逻辑全部写在 Realm 中
-
AuthenticationInfo:
用户的角色信息集合,认证时使用
-
AuthorzationInfo:
角色的权限信息集合,授权时使用
-
DefaultWebSecurityManager:
安全管理器,开发者自定义的 Realm 需要注入到 DefaultWebSecurityManager 进行管理才能生效
-
ShiroFilterFactoryBean:
过滤器工厂,Shiro 的基本运行机制是开发者定制规则,Shiro 去执行,具体的执行操作就是由 ShiroFilterFactoryBean 创建的一个个 Filter 对象来完成
3. Shiro运行机制
4. SpringBoot 整合 Shiro
(1) 创建 SpringBoot 应用,集成 Shiro 等相关组件
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.5.3</version>
</dependency>
(2) 自定义 Shiro 过滤器
package com.lichun.realm;
import com.lichun.entity.Account;
import com.lichun.service.AccountService;
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.HashSet;
import java.util.Set;
/**
* @Author: xinglibao
* @Date: 2021/10/19 8:29
* 自定义Shiro过滤器
*/
public class AccountRealm extends AuthorizingRealm {
@Autowired
private AccountService accountService;
/**
* 授权
* @param principalCollection 角色的权限信息集合,授权时使用
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
// 获取当前用户的登录信息
Subject subject = SecurityUtils.getSubject();
Account account = (Account) subject.getPrincipal();
// 设置角色
Set<String> roles = new HashSet<>();
roles.add(account.getRole());
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roles);
// 设置权限
info.addStringPermission(account.getPerms());
return info;
}
/**
* 认证
* @param authenticationToken 用户的角色信息集合,认证时使用
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
/**
* public class UsernamePasswordToken implements HostAuthenticationToken, RememberMeAuthenticationToken
* public interface HostAuthenticationToken extends AuthenticationToken
*/
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
Account account = accountService.findByUsername(token.getUsername());
if (account != null) {
/**
* public SimpleAuthenticationInfo(Object principal, Object credentials, String realmName)
*
* public abstract class AuthorizingRealm extends AuthenticatingRealm
* implements Authorizer, Initializable, PermissionResolverAware, RolePermissionResolverAware
* public abstract class AuthenticatingRealm extends CachingRealm implements Initializable
* public abstract class CachingRealm implements Realm, Nameable, CacheManagerAware, LogoutAware
*
* getName()方法在CachingRealm中
* 而AuthenticatingRealm继承了CachingRealm,AuthorizingRealm继承了AuthenticatingRealm,AuthorizingRealm extends AuthenticatingRealm
* 最终AccountRealm继承了AuthorizingRealm
*/
return new SimpleAuthenticationInfo(account, account.getPassword(), getName());
}
return null;
}
}
(3) 配置类
package com.lichun.config;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import com.lichun.realm.AccountRealm;
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 org.springframework.context.annotation.Configuration;
import java.util.Hashtable;
import java.util.Map;
/**
* @Author: xinglibao
* @Date: 2021/10/19 8:28
*/
@Configuration
public class ShiroConfig {
/**
* dialect 方言
* @return
*/
@Bean
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean
(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 编写认证和授权规则
Map<String, String> map = new Hashtable<>();
/**
* 必须登录才能访问main.html
* authc: 必须认证
*/
map.put("/main", "authc");
/**
* perms: 必须拥有某个权限才能访问
* 当前用户必须拥有manage授权才能访问manage.html
*/
map.put("/manage","perms[manage]");
/**
* role: 必须拥有某个角色才能访问
* 当前用户必须拥有administrator角色才能访问administrator.html
*/
map.put("/administrator","roles[administrator]");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
// 设置登录页面
shiroFilterFactoryBean.setLoginUrl("/login");
// 设置未授权页面
shiroFilterFactoryBean.setUnauthorizedUrl("/unauth");
return shiroFilterFactoryBean;
}
@Bean
public DefaultWebSecurityManager securityManager
(@Qualifier("accountRealm") AccountRealm accountRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(accountRealm);
return securityManager;
}
@Bean
public AccountRealm accountRealm() {
return new AccountRealm();
}
}
(4) 编写认证和授权规则
认证过滤器
- anon:无需认证
- authc:必须认证
- authcBasic:需要通过 HTTPBasic 认证
- user:不一定通过认证,只要曾经被 Shiro 记录即可,比如:记住我。
授权过滤器
- perms:必须拥有某个权限才能访问
- role:必须拥有某个角色才能访问
- port:请求的端口必须是指定值才可以
- rest:请求必须基于 RESTful,POST、PUT、GET、DELETE
- ssl:必须是安全的 URL 请求,协议 HTTPS
5. Shiro 整合 Tymeleaf
(1) 引入依赖
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
(2 )配置类添加 ShiroDialect
@Bean
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
(3) index.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro">
<head>
<meta charset="UTF-8">
<title>index</title>
<!--不加下面这一行会报错,但是没影响-->
<link rel="shortcut icon" href="#"/>
<!--这个页面的主要功能就是如果你已经登陆过了,来到此页面会显示欢迎回来
并且这个页面会根据你是否有相应的角色、权限给你显示菜单-->
</head>
<body>
<h1>index</h1>
<div th:if="${session.account != null}">
<span th:text="${session.account.username} + '欢迎回来'"></span>
<a href="/logout">退出</a>
</div>
<a href="/main">main</a><br/>
<div shiro:hasPermission="manage">
<a href="/manage">manage</a><br/>
</div>
<div shiro:hasRole="administrator">
<a href="/administrator">administrator</a>
</div>
</body>
</html>