Shiro是一个功能强大且易于使用的Java安全框架,可执行身份验证、授权、加密和会话管理。 使用Shiro易于理解的API,您可以快速轻松地保护任何应用程序,从最小的移动应用程序到最大的web和企业应用程序。
Shiro和JWt
课程地址:【【编程不良人】2020最新版Shiro教程,整合SpringBoot项目实战教程】
一、Shiro
1.1、权限管理
什么是权限管理
权限管理包括用户身份认证和授权两部分,简称认证授权。对于
需要访问控制的资源用户首先经过身份认证,认证通过后用户
具有该资源的访问权限方可访问
什么是身份认证:Authenticator
身份认证,就是判断一个用户是否为合法用户的处理过程。
最常用的简单身份认证方式是系统通过核对用户输入的用
户名和口令,看其是否与系统中存储的该用户的用户名和
口令一致,来判断用户身份是否正确。对于采用指纹等
系统,则出示指纹;对于硬件Key等刷卡系统,则需要刷卡。
什么是授权:Authorizer
授权,即访问控制,控制谁能访问哪些资源。主体进行身份认
证后需要分配权限方可访问系统的资源,对于某些资源没有
权限是无法访问的
1.2、Shiro核心架构
Subject
Subject即主体,外部应用与subject进行交互,subject记录了当前
操作用户,将用户的概念理解为当前操作的主体,可能是一个通过浏
览器请求的用户,也可能是一个运行的程序。 Subject在shiro中
是一个接口,接口中定义了很多认证授相关的方法,外部程序通
过subject进行认证授,而subject是通过SecurityManager安全
管理器进行认证授权
SecurityManager
SecurityManager即安全管理器,对全部的subject进行安全管理,
它是shiro的核心,负责对所有的subject进行安全管理。通过SecurityManager可以完成subject的认证、授权等.
实质上SecurityManager是通过Authenticator进行认证,通过Authorizer进行授权,通过SessionManager进行会话管理等。
SecurityManager是一个接口,继承了Authenticator, Authorizer,SessionManager这三个接口。
Authenticator
Authenticator即认证器,对用户身份进行认证,Authenticator是一个接口.
shiro提供ModularRealmAuthenticator实现类,通过ModularRealmAuthenticator基本上可以满足大多数需求,也可以自定义认证器。
Authorizer
Authorizer即授权器,用户通过认证器认证通过,在访问功能时需要通过授权器判断用户是否有此功能的操作权限。
Realm
Realm即领域,相当于datasource数据源,securityManager进行安全认证需要通过Realm获取用户权限数据.
比如:如果用户身份数据在数据库那么realm就需要从数据库获取用户身份信息。
注意:不要把realm理解成只是从数据源取数据,在realm中还有认证授权校验的相关的代码。
SessionManager
sessionManager即会话管理,shiro框架定义了一套会话管理,它不依赖web容器的session.
所以shiro可以使用在非web应用上,也可以将分布式应用的会话集中在一点管理,此特性可使它实现单点登录。
SessionDAO
SessionDAO即会话dao,是对session会话操作的一套接口,比如要将session存储到数据库,可以通过jdbc将会话存储到数据库
CacheManager
CacheManager即缓存管理,将用户权限数据存储在缓存,这样可以提高性能
Cryptography
Cryptography即密码管理,shiro提供了一套加密/解密的组件,方便开发。比如提供常用的散列、加/解密等功能
1.3、Shiro中的认证
1、创建maven项目,引入依赖
org.apache.shiro
shiro-core
1.11.0
2、引入shiro配置文件并加入如下配置:shiro.ini
[users]
xiaochen=123
zhangsan=456
3、认证代码
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.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.text.IniRealm;
import org.apache.shiro.subject.Subject;
public class TestAuthenticator {
public static void main(String[] args) {
//1.创建安全管理器对象
DefaultSecurityManager securityManager = new DefaultSecurityManager();
//2.给安全管理器设置realm
securityManager.setRealm( new IniRealm( "classpath:shiro.ini" ));
//3.SecurityUtils 给全局安全工具类设置安全管理器
SecurityUtils.setSecurityManager(securityManager);
//4.关键对象 subject 主体
Subject subject = SecurityUtils.getSubject();
//5.创建令牌
UsernamePasswordToken token = new UsernamePasswordToken( "xiaochen1" , "123" );
try {
System. out .println( "认证状态: " + subject.isAuthenticated());
//用户认证
subject.login(token);
System. out .println( "认证状态: " + subject.isAuthenticated());
} catch (UnknownAccountException e){
e.printStackTrace();
System. out .println( "认证失败: 用户名不存在~" );
} catch (IncorrectCredentialsException e){
e.printStackTrace();
System. out .println( "认证失败: 密码错误~" );
}
}
}
4、异常
DisabledAccountException(帐号被禁用)
LockedAccountException(帐号被锁定)
ExcessiveAttemptsException(登录失败次数过多)
ExpiredCredentialsException(凭证过期)等
1.3.1、自定义Realm
上边的程序使用的是Shiro自带的IniRealm,IniRealm从ini配置文件中读取用户的信息,大部分情况下需要从系统的数据库中读取用户信息,所以需要自定义realm。
Shiro提供的Realm
自定义Realm实现AuthorizingRealm:源码
public class SimpleAccountRealm extends AuthorizingRealm {
//.......省略
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
SimpleAccount account = getUser(upToken.getUsername());
if (account != null ) {
if (account.isLocked()) {
throw new LockedAccountException( "Account [" + account + "] is locked." );
}
if (account.isCredentialsExpired()) {
String msg = "The credentials for account [" + account + "] are expired" ;
throw new ExpiredCredentialsException(msg);
}
}
return account;
}
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String username = getUsername(principals);
USERS_LOCK.readLock(). lock ();
try {
return this .users. get (username);
} finally {
USERS_LOCK.readLock().unlock();
}
}
}
自定义Realm
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.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
/**
* @ClassName ConsumerRealm
* @Author zhangzhixi
* @Description
* @Date 2023-03-12 22:17
* @Version 1.0
*/
public class ConsumerRealm extends AuthorizingRealm {
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null ;
}
/**
* 认证
*
* @param token 用户名密码token
* @return 认证实例
* @throws AuthenticationException 身份验证异常
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 通过token获取用户名
String userName = token.getPrincipal().toString();
// 这里模拟从数据库中验证用户,之后可能从JDBC、Mybatis取
if ( "xiaochen" . equals (userName)) {
// 参数1:返回数据库中正确的用户名 参数2:返回数据库中正确密码 参数3:提供当前realm的名字 this.getName();
return new SimpleAuthenticationInfo(userName, "1234" , this .getName());
}
return null ;
}
}
测试
public class TestConsumerRealm {
public static void main(String[] args) {
//创建securityManager
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
//设置自定义realm
defaultSecurityManager.setRealm( new ConsumerRealm());
//将安全工具类设置安全工具类
SecurityUtils.setSecurityManager(defaultSecurityManager);
//通过安全工具类获取subject
Subject subject = SecurityUtils.getSubject();
//创建token
UsernamePasswordToken token = new UsernamePasswordToken( "xiaochen" , "123" );
//执行登录
try {
subject.login(token);
} catch (UnknownAccountException unknownAccountException) {
unknownAccountException.printStackTrace();
System. out .println( "用户名验证错误" );
} catch (IncorrectCredentialsException incorrectCredentialsException) {
incorrectCredentialsException.printStackTrace();
System. out .println( "密码验证错误" );
}
}
}
1.3.2、使用MD5+盐(Salt),实现认证
1、创建用户名和密码时候使用的加盐规则
import org.apache.shiro.crypto.hash.Md5Hash;
/**
* @ClassName ShiroMd5Utils
* @Author zhangzhixi
* @Description
* @Date 2023-03-12 23:13
* @Version 1.0
*/
public class ShiroMd5Utils {
public static void main(String[] args) {
// 使用用户名做盐值,为了让盐是动态的
String userName = "xiaochen" ;
//使用md5
Md5Hash md5Hash = new Md5Hash( "123" );
System. out .println(md5Hash.toHex());
//使用MD5 + salt处理
Md5Hash md5Hash1 = new Md5Hash( "123" , userName);
System. out .println(md5Hash1.toHex());
//使用md5 + salt + hash散列
Md5Hash md5Hash2 = new Md5Hash( "123" , userName, 1024);
System. out .println(md5Hash2.toHex());
}
}
2、设置自定义:MD5+盐,的认证代码
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.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
/**
* 使用自定义realm 加入md5 + salt +hash
*/
public class CustomerMd5Realm extends AuthorizingRealm {
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null ;
}
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//获取身份信息
String principal = token.getPrincipal().toString();
//根据用户名查询数据库
if ( "xiaochen" . equals (principal)) {
// 参数1: 数据库用户名
// 参数2:数据库md5+salt之后的密码
// 参数3:注册时的随机盐,本例子中使用动态的,用户名作为随机盐
// 参数4:realm的名字
return new SimpleAuthenticationInfo(
principal,
"847b2f7a3705be61f6738b48bed195f8" ,
ByteSource.Util.bytes(principal),
this .getName());
}
return null ;
}
}
3、测试
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.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.subject.Subject;
public class TestCustomerMd5RealmAuthenicator {
public static void main(String[] args) {
// 1、创建安全管理器
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
// 2、注入realm
CustomerMd5Realm realm = new CustomerMd5Realm();
// 3、设置realm使用hash凭证匹配器
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
// 使用算法
credentialsMatcher.setHashAlgorithmName( "md5" );
// 散列次数
credentialsMatcher.setHashIterations(1024);
// 4、设置凭据匹配器
realm.setCredentialsMatcher(credentialsMatcher);
defaultSecurityManager.setRealm(realm);
// 5、将安全管理器注入安全工具
SecurityUtils.setSecurityManager(defaultSecurityManager);
// 6、通过安全工具类获取subject
Subject subject = SecurityUtils.getSubject();
// 7、认证
UsernamePasswordToken token = new UsernamePasswordToken( "xiaochen" , "123" );
try {
subject.login(token);
System. out .println( "登录成功" );
} catch (UnknownAccountException e) {
e.printStackTrace();
System. out .println( "用户名错误" );
} catch (IncorrectCredentialsException e){
e.printStackTrace();
System. out .println( "密码错误" );
}
}
}
1.4、Shiro中的授权
授权,即访问控制,控制谁能访问哪些资源。主体进行身份认证后需要分配权限方可访问系统的资源,对于某些资源没有权限是无法访问的。
1.4.1、关键对象
Who,即主体(Subject),主体需要访问系统中的资源。
What,即资源(Resource),如系统菜单、页面、按钮、类方法、
系统商品信息等。资源包括资源类型和资源实例,比如商品信息
为资源类型,类型为t01的商品为资源实例,编号为001的商品信息
也属于资源实例。
How,权限/许可(Permission),规定了主体对资源的操作许可,
权限离开资源没有意义,如用户查询权限、用户添加权限、某个
类方法的调用权限、编号为001用户的修改权限等,通过权限可
知主体对哪些资源都有哪些操作许可。
1.4.2、授权流程
1.4.3、授权方式
基于角色的访问控制RBAC基于角色的访问控制(Role-Based Access Control)是以角色为中心进行访问控制
if (subject.hasRole( "admin" )){
//操作什么资源
}
基于资源的访问控制RBAC基于资源的访问控制(Resource-Based Access Control)是以资源为中心进行访问控制
if (subject.isPermission( "user:update:01" )){ //资源实例
//对01用户进行修改
}
if (subject.isPermission( "user:update:*" )){ //资源类型
//对01用户进行修改
}
1.4.4、权限字符串
权限字符串的规则是:
资源标识符:操作:资源实例标识符
意思是对哪个资源的哪个实例具有什么操作,“:”是资源/操作/实例的分割符,权限字符串也可以使用*通配符。
例子:
用户创建权限:user:create,或user:create:*
用户修改实例001的权限:user:update:001
用户实例001的所有权限:user:*:001
1.4.5、自定义Realm重写授权
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 java.util.Arrays;
/**
* 使用自定义realm 加入md5 + salt +hash
*/
public class CustomerMd5Realm extends AuthorizingRealm {
/**
* 授权
* @param principals subject实体
* @return 授权对象
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 1、获取当前身份信息
String userName = principals.getPrimaryPrincipal().toString();
// 2、获取权限对象
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
// 3、添加角色(从数据库中获取)
authorizationInfo.addRoles(Arrays.asList("admin", "user"));
// 4、添加权限:userName这个用户在user模块具有所有权限
authorizationInfo.addStringPermissions(Arrays.asList("user:*:" + userName));
return authorizationInfo;
}
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//获取身份信息
String principal = token.getPrincipal().toString();
//根据用户名查询数据库
if ("xiaochen".equals(principal)) {
// 参数1: 数据库用户名
// 参数2:数据库md5+salt之后的密码
// 参数3:注册时的随机盐,本例子中使用动态的,用户名作为随机盐
// 参数4:realm的名字
return new SimpleAuthenticationInfo(
principal,
"847b2f7a3705be61f6738b48bed195f8",
ByteSource.Util.bytes(principal),
this.getName());
}
return null;
}
}
1.4.6、测试授权
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.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.subject.Subject;
import java.util.Arrays;
public class TestCustomerMd5RealmAuthenicator {
public static void main(String[] args) {
// 1、创建安全管理器
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
// 2、注入realm
CustomerMd5Realm realm = new CustomerMd5Realm();
// 3、设置realm使用hash凭证匹配器
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
// 使用算法
credentialsMatcher.setHashAlgorithmName("md5");
// 散列次数
credentialsMatcher.setHashIterations(1024);
// 4、设置凭据匹配器
realm.setCredentialsMatcher(credentialsMatcher);
defaultSecurityManager.setRealm(realm);
// 5、将安全管理器注入安全工具
SecurityUtils.setSecurityManager(defaultSecurityManager);
// 6、通过安全工具类获取subject
Subject subject = SecurityUtils.getSubject();
// 7、认证
UsernamePasswordToken token = new UsernamePasswordToken("xiaochen", "123");
try {
subject.login(token);
System.out.println("登录成功");
} catch (UnknownAccountException e) {
e.printStackTrace();
System.out.println("用户名错误");
} catch (IncorrectCredentialsException e) {
e.printStackTrace();
System.out.println("密码错误");
}
// 判断是否已经认证
if (subject.isAuthenticated()) {
System.out.println("************基于角色的控制************");
// 基于角色的权限控制,是否有admin角色
System.out.println(subject.hasRole("admin"));
// 基于角色的权限控制,是否有其中的一种角色信息
System.out.println(Arrays.toString(subject.hasRoles(Arrays.asList("admin", "super"))));
// 基于角色的权限控制,是否同时具有提供的所有权限
System.out.println(subject.hasAllRoles(Arrays.asList("admin", "super")));
try {
System.out.println("************基于权限的控制************");
// 资源标识符:操作:资源类型,比如当前用户是否对user模块具有所有权限就可以这样写(user:*:xiaochen)
System.out.println(subject.isPermitted("user:*:xiaochen"));
// 分别具有哪些权限
System.out.println(Arrays.toString(subject.isPermitted("user:*:xiaochen", "user:update:xiaochen")));
// 同时具有哪些权限
System.out.println(subject.isPermittedAll("user:*:xiaochen", "commodity:update:xiaochen"));
} catch (AuthorizationException e) {
e.printStackTrace();
System.out.println("未经授权,无法访问");
}
}
}
}
1.5、常见过滤器
配置缩写 | 对应的过滤器 | 功能 |
anon | AnonymousFilter | 指定url可以匿名访问 |
authc | FormAuthenticationFilter | 指定url需要form表单登录,默认会从请求中获取 |
authcBasic | BasicHttpAuthenticationFilter | 指定url需要basic登录 |
logout | LogoutFilter | 登出过滤器,配置指定url就可以实现退出功能,非常方便 |
noSessionCreation | NoSessionCreationFilter | 禁止创建会话 |
perms | PermissionsAuthorizationFilter | 需要指定权限才能访问 |
port | PortFilter | 需要指定端口才能访问 |
rest | HttpMethodPermissionFilter | 将http请求方法转化成相应的动词来构造一个权限字符串,这个感觉意义不大,有兴趣自己看源码的注释 |
roles | RolesAuthorizationFilter | 需要指定角色才能访问 |
ssl | SslFilter | 需要https请求才能访问 |
user | UserFilter | 需要已登录或“记住我”的用户才能访问 |
1.6、Shiro整合SpringBoot(JSP)
1.6.1、引入依赖
org.apache.tomcat.embed
tomcat-embed-jasper
javax.servlet.jsp.jstl
jstl-api
1.2
org.apache.shiro
shiro-spring-boot-starter
1.5.3
1.6.2、配置项目
server.servlet.context-path=/
spring.mvc.view.prefix=/
spring.mvc.view.suffix=.jsp
1.6.3、编写jsp页面
├─resources
│ │ application.properties
│ │ rebel.xml
│ │
│ ├─ static
│ └─templates
└─webapp
index.jsp -- 受限资源
login.jsp -- 公共资源
1.6.4、自定义Realm
public class CustomerRealm extends AuthorizingRealm {
//处理授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null ;
}
//处理认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws
AuthenticationException {
return null ;
}
}
1.6.5、配置Shiro
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.Realm;
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.HashMap;
import java.util.Map;
/**
* @ClassName ShiroConfig
* @Author zhangzhixi
* @Description
* @Date 2023-03-15 16:35
* @Version 1.0
*/
@Configuration
public class ShiroConfig {
/**
* shiro过滤器工厂
* @param securityManager shiro安全管理器
* @return 过滤器
*/
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier( "securityManager" ) SecurityManager securityManager) {
// 1、创建shiro的filter
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 2、注入安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<string, string=""> authMap = new HashMap<>();
// 配置公共资源
// 配置受限资源
authMap.put( "/index.jsp" , "authc" );
// 如果是受限资源,默认会重定向login.jsp
//shiroFilterFactoryBean.setLoginUrl("/login.jsp");
shiroFilterFactoryBean.setFilterChainDefinitionMap(authMap);
return shiroFilterFactoryBean;
}
/**
* shiro的安全管理器
* @param realm realm
* @return shiro的安全管理器
*/
@Bean( "securityManager" )
public DefaultWebSecurityManager getSecurityManager(@Qualifier( "realm" ) Realm realm) {
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
defaultWebSecurityManager.setRealm(realm);
return defaultWebSecurityManager;
}
/**
* 创建自定义realm
* @return 自定义Realm
*/
@Bean( "realm" )
public Realm getRealm() {
return new CustomerRealm();
}
}</string,>
1.6.6、测试
1.6.7、认证与退出登录
登录表单:login.jsp
<h1>登录页面</h1>
<form action="${pageContext.request.contextPath}/user/login" method="post">
<label>
用户名:
<input type="text" name="username">
</label> <br/>
<label>
密码 :
<input type="text" name="password">
</label> <br>
<input type="submit" value="登录">
</form>
受限页面:index.jsp
<h2>系统主页v1.0(受限页面)</h2>
<label>
<a href="${pageContext.request.contextPath}/user/logout">退出登录</a>
</label>
<ul>
<li>用户管理</li>
<li>商品管理</li>
<li>订单管理</li>
<li>物流管理</li>
</ul>
</body>
</html>
配置拦截路径:ShiroConfig
/**
* shiro过滤器工厂
* @param securityManager shiro安全管理器
* @return 过滤器
*/
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") SecurityManager securityManager) {
// 1、创建shiro的filter
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 2、注入安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String, String> authMap = new HashMap<>();
// 配置公共资源
authMap.put("/login.jsp", "anon");
// 配置受限资源
authMap.put("/index.jsp", "authc");
authMap.put("/", "authc");
// 如果是受限资源,默认会重定向login.jsp
//shiroFilterFactoryBean.setLoginUrl("/login.jsp");
shiroFilterFactoryBean.setFilterChainDefinitionMap(authMap);
return shiroFilterFactoryBean;
}
自定义Realm处理认证:ConsumerRealm
public class CustomerRealm extends AuthorizingRealm {
//处理授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
//处理认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("==========================");
String principal = (String) token.getPrincipal();
// 后序这里的用户名密码,从数据库中查
if ("xiaochen".equals(principal)) {
return new SimpleAuthenticationInfo(principal, "123", this.getName());
}
return null;
}
}
web请求:UserController
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.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
@RequestMapping("user")
public class UserController {
/**
* 用来处理身份认证
*
* @param username 用户名
* @param password 密码
* @return 登录成功返回页面
*/
@RequestMapping(value = "login",method = RequestMethod.POST)
public String login(String username, String password) {
//获取主体对象
Subject subject = SecurityUtils.getSubject();
try {
subject.login(new UsernamePasswordToken(username, password));
return "redirect:/index.jsp";
} catch (UnknownAccountException e) {
e.printStackTrace();
System.out.println("用户名错误!");
} catch (IncorrectCredentialsException e) {
e.printStackTrace();
System.out.println("密码错误!");
}
return "redirect:/login.jsp";
}
/**
* 退出系统
*/
@GetMapping("/logout")
public String logout() {
Subject subject = SecurityUtils.getSubject();
subject.logout();
return "redirect:/login.jsp";
}
}
2.3、认证(MD5+Sale)(连接数据库,操作Mybatis)
1、注册页面
<%@page contentType="text/html; UTF-8" pageEncoding="UTF-8" isELIgnored="false" %>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h1>注册页面</h1>
<form action="${pageContext.request.contextPath}/user/register" method="post">
<!--指定实际发送的请求方式-->
<input type="hidden" name="_method" value="PUT">
<label>
用户名:
<input type="text" name="username">
</label> <br/>
<label>
密码 :
<input type="text" name="password">
</label> <br>
<input type="submit" value="注册">
</form>
</body>
</html>
2、登录页面
<%@page contentType="text/html; UTF-8" pageEncoding="UTF-8" isELIgnored="false" %>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h1>登录页面</h1>
<form action="${pageContext.request.contextPath}/user/login" method="post">
<label>
用户名:
<input type="text" name="username">
</label> <br/>
<label>
密码 :
<input type="text" name="password">
</label> <br>
<input type="submit" value="登录"> <a href="/register.jsp">没有账号?点我注册</a>
</form>
</body>
</html>
3、Controller
@Autowired
private UserService userService;
/**
* 用来处理身份认证
*
* @return 登录成功返回页面
*/
@RequestMapping(value = "login", method = RequestMethod.POST)
public String login(User user) {
//获取主体对象
Subject subject = SecurityUtils.getSubject();
try {
subject.login(new UsernamePasswordToken(user.getUsername(), user.getPassword()));
return "redirect:/index.jsp";
} catch (UnknownAccountException e) {
e.printStackTrace();
System.out.println("用户名错误!");
} catch (IncorrectCredentialsException e) {
e.printStackTrace();
System.out.println("密码错误!");
}
return "redirect:/login.jsp";
}
/**
* 退出系统
*/
@GetMapping("/logout")
public String logout() {
Subject subject = SecurityUtils.getSubject();
subject.logout();
return "redirect:/login.jsp";
}
/**
* 用户注册
* @param user 用户实体
* @return 注册成功返回登录页面,注册失败返回注册页面
*/
@PutMapping("/register")
public String saveUser(User user) {
int count = userService.saveUser(user);
if (count == 1) {
return "redirect:/login.jsp";
}
return "redirect:/register.jsp";
}
@RequestMapping("save")
public String save() {
//获取主体对象
Subject subject = SecurityUtils.getSubject();
// 基于角色权限控制
if (subject.hasRole("admin")) {
System.out.println("保存订单!");
} else {
System.out.println("无权访问!");
}
//基于权限字符串判断是否有权限访问
subject.isPermitted("admin", "user");
return "redirect:/index.jsp";
}
4、mapper
<insert id="saveUser" parameterType="com.zhixi.pojo.User" useGeneratedKeys="true">
insert into shiro.t_user(username, password, salt)
values (#{username}, #{password}, #{salt});
</insert>
5、service层,对密码字段进行md5+Sale+Hash
@Autowired
private UserMapper userMapper;
@Override
public int saveUser(User user) {
//处理业务调用dao
//1.生成随机盐
String salt = SaltUtils.getSalt(8);
//2.将随机盐保存到数据
user.setSalt(salt);
//3.明文密码进行md5 + salt + hash散列
Md5Hash md5Hash = new Md5Hash(user.getPassword(), salt, 1024);
user.setPassword(md5Hash.toHex());
return userMapper.saveUser(user);
}
6、自定义Realm处理登录
/**
* 处理认证
* @param token 用户登录信息
* @return 身份验证信息
* @throws AuthenticationException 异常
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 获取当前登录的用户名
String principal = token.getPrincipal().toString();
// 查询数据库
User user = userService.selectUserByName(principal);
return new SimpleAuthenticationInfo(user, user.getPassword(), ByteSource.Util.bytes(user.getSalt()), this.getName());
}
7、Shiro配置:配置请求过滤以及自定义Realm使用到的加密算法以及散列次数
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.Realm;
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.HashMap;
import java.util.Map;
/**
* @ClassName ShiroConfig
* @Author zhangzhixi
* @Description
* @Date 2023-03-15 16:35
* @Version 1.0
*/
@Configuration
public class ShiroConfig {
/**
* shiro过滤器工厂
* @param securityManager shiro安全管理器
* @return 过滤器
*/
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") SecurityManager securityManager) {
// 1、创建shiro的filter
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 2、注入安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String, String> authMap = new HashMap<>();
// 配置公共资源
authMap.put("/login.jsp", "anon");
authMap.put("/user/login", "anon");
authMap.put("/register.jsp", "anon");
authMap.put("/user/register", "anon");
// 配置受限资源
authMap.put("/**", "authc");
authMap.put("/index.jsp", "authc");
authMap.put("/", "authc");
// 如果是受限资源,默认会重定向login.jsp
//shiroFilterFactoryBean.setLoginUrl("/login.jsp");
shiroFilterFactoryBean.setFilterChainDefinitionMap(authMap);
return shiroFilterFactoryBean;
}
/**
* shiro的安全管理器
* @param realm realm
* @return shiro的安全管理器
*/
@Bean("securityManager")
public DefaultWebSecurityManager getSecurityManager(@Qualifier("realm") Realm realm) {
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
defaultWebSecurityManager.setRealm(realm);
return defaultWebSecurityManager;
}
/**
* 创建自定义realm
* @return 自定义Realm
*/
@Bean("realm")
public Realm getRealm(){
CustomerRealm customerRealm = new CustomerRealm();
//设置hashed凭证匹配器
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
//设置md5加密
credentialsMatcher.setHashAlgorithmName("md5");
//设置散列次数
credentialsMatcher.setHashIterations(1024);
customerRealm.setCredentialsMatcher(credentialsMatcher);
return customerRealm;
}
}
8、测试
添加用户:
ShiroConfig->register.jsp-->Controller->入库
用户登录:
ShiroConfig-->login.jsp-->Controller-->自定义Realm处理认证
2.4、授权(不操作DB)
2.4.1、Shiro中实现授权方式
// 编码式
Subject subject = SecurityUtils.getSubject();
if(subject.hasRole(“admin”)) {
//有权限
} else {
//无权限
}
// 注解式
@RequiresRoles("admin")
public void hello() {
//有权限
}
// 标签式
JSP/GSP 标签:在JSP/GSP 页面通过相应的标签完成:
<shiro:hasRole name="admin">
<!— 有权限—>
</shiro:hasRole>
注意: Thymeleaf 中使用shiro需要额外集成!
2.4.2、实现授权-自定义realm
/**
* 处理授权
* @param principals 权限数据
* @return 返回的授权信息
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 获取登录用户名
String userName = principals.getPrimaryPrincipal().toString();
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
if ("admin".equals(userName)) {
// 添加角色
simpleAuthorizationInfo.addRoles(Arrays.asList("admin", "user"));
// 添加资源
simpleAuthorizationInfo.addStringPermissions(Arrays.asList("user:*:*", "admin:*:*"));
}
return simpleAuthorizationInfo;
}
2.4.3、实现授权-页面授权方式
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<%@page contentType="text/html; UTF-8" pageEncoding="UTF-8" isELIgnored="false" %>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h2>系统主页v1.0(受限页面)</h2>
<label>
<a href="${pageContext.request.contextPath}/user/logout">退出登录</a>
</label>
<shiro:hasAnyRoles name="user,admin">
<li><a href="">用户管理(满足user/admin权限)</a>
<ul>
<shiro:hasPermission name="user:add:*">
<li><a href="">添加</a></li>
</shiro:hasPermission>
<shiro:hasPermission name="user:delete:*">
<li><a href="">删除</a></li>
</shiro:hasPermission>
<shiro:hasPermission name="user:update:*">
<li><a href="">修改</a></li>
</shiro:hasPermission>
<shiro:hasPermission name="user:find:*">
<li><a href="">查询</a></li>
</shiro:hasPermission>
</ul>
</li>
</shiro:hasAnyRoles>
<hr>
<shiro:hasRole name="admin">
<li><a href="">用户管理(满足admin权限)</a>
<ul>
<li><a href="">商品管理</a></li>
<li><a href="">订单管理</a></li>
<li><a href="">物流管理</a></li>
</ul>
</shiro:hasRole>
</body>
</html>
登录后即可看到页面上面展示的列表数据。
2.4.4、实现授权-代码授权方式
@RequestMapping("save")
public String save(){
//获取主体对象
Subject subject = SecurityUtils.getSubject();
// 基于角色权限控制
if (subject.hasRole("admin")) {
System.out.println("保存订单!");
}else{
System.out.println("无权访问!");
}
//基于权限字符串判断是否有权限访问
subject.isPermitted("admin", "user");
return "redirect:/index.jsp";
}
2.4.5、实现授权-注解授权方式
@RequiresRoles 用来基于角色进行授权
@RequiresPermissions 用来基于权限进行授权
// 用来判断角色:具有 admin/user
@RequiresRoles(value = {"admin", "user"}, logical = Logical.OR)
// 用来判断权限字符串
@RequiresPermissions("user:update:01")
@RequestMapping("saveToPermissions")
public String saveToPermissions() {
return "redirect:/index.jsp";
}
2.5、授权-基于角色(连接数据库,操作Mybatis)
1、数据库表设计
2、实体
@Data
public class User {
private Integer id;
private String username;
private String password;
private String salt;
/**
* 用户角色信息
*/
private List<Role> roles;
}
@Data
public class Role {
/**
* 主键
*/
private Integer id;
/**
* 角色名称
*/
private String name;
}
@Data
public class Perms {
private Integer id;
/**
* 权限字符串
*/
private String name;
/**
* 权限url
*/
private String url;
}
3、根据用户名查询角色信息
/**
* 根据用户名查询角色信息
* @param username 用户名
* @return 用户角色集合
*/
List<User> findRolesByUsernameRoles(String username);
4、mapper文件
<resultMap id="userRoleMap" type="com.zhixi.pojo.User">
<id column="id" property="id"/>
<result column="username" property="username"/>
<collection property="roles" javaType="list" ofType="com.zhixi.pojo.Role">
<result column="roleid" property="id"/>
<result column="rname" property="name"/>
</collection>
</resultMap>
<select id="findRolesByUsernameRoles" resultMap="userRoleMap">
SELECT t.id, t.username, ur.roleid, r.`name` rname
FROM shiro.t_user t
LEFT JOIN shiro.t_user_role ur ON t.id = ur.userid
LEFT JOIN shiro.t_role r ON r.id = ur.roleid
WHERE t.username = #{username}
</select>
5、修改自定义Realm中自定义授权代码
import com.zhixi.pojo.User;
import com.zhixi.sevice.UserService;
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 org.springframework.stereotype.Component;
import java.util.List;
import java.util.Optional;
@Component
public class CustomerRealm extends AuthorizingRealm {
@Autowired
UserService userService;
/**
* 处理授权
*
* @param principals 权限数据
* @return 返回的授权信息
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 获取登录用户名(从认证那里传过来的)
User userLogin = (User)principals.getPrimaryPrincipal();
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
// 查询登录用户权限集合
List<User> userList = userService.findRolesByUsernameRoles(userLogin.getUsername());
Optional.ofNullable(userList).ifPresent(users -> users.forEach(user -> user.getRoles().forEach(role -> simpleAuthorizationInfo.addRole(role.getName()))));
// 添加角色权限
System.out.println(userList);
return simpleAuthorizationInfo;
}
//处理认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("==========================");
// 获取当前登录的用户名
String principal = token.getPrincipal().toString();
// 查询数据库
User user = userService.selectUserByName(principal);
return new SimpleAuthenticationInfo(user, user.getPassword(), ByteSource.Util.bytes(user.getSalt()), this.getName());
}
}
6、受限页面:index.jsp
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<%@page cnotallow="text/html; UTF-8" pageEncoding="UTF-8" isELIgnored="false" %>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
cnotallow="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" cnotallow="ie=edge">
<title>Document</title>
</head>
<body>
<h2>系统主页v1.0(受限页面)</h2>
<label>
<a href="${pageContext.request.contextPath}/user/logout">退出登录</a>
</label>
<%--获取当前登录的用户名--%>
<shiro:authenticated>
<%--填写的是放在认证,SimpleAuthenticationInfo中的user数据--%>
<h3>
当前登录用户是:<shiro:principal property="username"/>
</h3>
</shiro:authenticated>
<shiro:hasAnyRoles name="user,admin">
<li><a href="">用户管理(满足user/admin权限)</a>
<ul>
<shiro:hasPermission name="user:add:*">
<li><a href="">添加</a></li>
</shiro:hasPermission>
<shiro:hasPermission name="user:delete:*">
<li><a href="">删除</a></li>
</shiro:hasPermission>
<shiro:hasPermission name="user:update:*">
<li><a href="">修改</a></li>
</shiro:hasPermission>
<shiro:hasPermission name="user:find:*">
<li><a href="">查询</a></li>
</shiro:hasPermission>
</ul>
</li>
</shiro:hasAnyRoles>
<hr>
<shiro:hasRole name="admin">
<li><a href="">用户管理(满足admin权限)</a>
<ul>
<li><a href="">商品管理</a></li>
<li><a href="">订单管理</a></li>
<li><a href="">物流管理</a></li>
</ul>
</shiro:hasRole>
</body>
</html>
7、测试
2.6、授权-基于权限(连接数据库,操作Mybatis)
1、DB
-- 权限表数据
INSERT INTO `shiro`.`t_pers` (`id`, `name`, `url`) VALUES (1, 'user:*:*', NULL);
INSERT INTO `shiro`.`t_pers` (`id`, `name`, `url`) VALUES (2, 'product:*:01', NULL);
INSERT INTO `shiro`.`t_pers` (`id`, `name`, `url`) VALUES (3, 'order:*:*', NULL);
-- 基于角色的权限,一个角色可以有多个权限
INSERT INTO `shiro`.`t_role_perms` (`id`, `roleid`, `permsid`) VALUES (1, 1, 1);
INSERT INTO `shiro`.`t_role_perms` (`id`, `roleid`, `permsid`) VALUES (2, 1, 2);
INSERT INTO `shiro`.`t_role_perms` (`id`, `roleid`, `permsid`) VALUES (3, 1, 3);
INSERT INTO `shiro`.`t_role_perms` (`id`, `roleid`, `permsid`) VALUES (4, 2, 1);
INSERT INTO `shiro`.`t_role_perms` (`id`, `roleid`, `permsid`) VALUES (5, 3, 2);
2、接口
/**
* 根据角色id查询权限集合
*
* @param id 角色ID
* @return 角色的权限集合
*/
List<Perms> findPermsByRoleId(Integer id);
3、 mapper
<resultMap id="roleToPerms" type="com.zhixi.pojo.Perms">
<result column="pers_name" property="name"/>
<result column="pers_url" property="url"/>
</resultMap>
<select id="findPermsByRoleId" resultMap="roleToPerms">
SELECT role.id,
role.name as role_name,
pers.`name` as pers_name,
pers.url as pers_url
FROM shiro.t_role role
LEFT JOIN shiro.t_role_perms rp ON role.id = rp.roleid
LEFT JOIN shiro.t_pers pers ON rp.permsid = pers.id
WHERE role.id = #{id}
</select>
4、自定义Realm-添加权限信息
/**
* 处理授权
*
* @param principals 权限数据
* @return 返回的授权信息
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 获取登录用户名(从认证那里传过来的)
User userLogin = (User) principals.getPrimaryPrincipal();
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
// 查询登录用户权限集合
List<User> userList = userService.findRolesByUsernameRoles(userLogin.getUsername());
Optional.ofNullable(userList).
ifPresent(users -> users.forEach(user -> {
user.getRoles().forEach(role -> {
// 添加角色信息
simpleAuthorizationInfo.addRole(role.getName());
// 根据角色ID查询权限集合
List<Perms> permsByRoleId = userService.findPermsByRoleId(role.getId());
// 添加权限信息
Optional.ofNullable(permsByRoleId).ifPresent(perms -> perms.forEach(per -> simpleAuthorizationInfo.addStringPermission(per.getName())));
}
);
}));
// 添加角色权限
return simpleAuthorizationInfo;
}
5、受限页面:index.jsp
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<%@page cnotallow="text/html; UTF-8" pageEncoding="UTF-8" isELIgnored="false" %>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
cnotallow="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" cnotallow="ie=edge">
<title>Document</title>
</head>
<body>
<h2>系统主页v1.0(受限页面)</h2>
<label>
<a href="${pageContext.request.contextPath}/user/logout">退出登录</a>
</label>
<%--获取当前登录的用户名--%>
<shiro:authenticated>
<%--填写的是放在认证,SimpleAuthenticationInfo中的user数据--%>
<h3>
当前登录用户是:<shiro:principal property="username"/>
</h3>
</shiro:authenticated>
<shiro:hasAnyRoles name="user,admin">
<li><a href="">用户管理(满足user/admin权限)</a>
<ul>
<shiro:hasPermission name="user:add:*">
<li><a href="">添加</a></li>
</shiro:hasPermission>
<shiro:hasPermission name="user:delete:*">
<li><a href="">删除</a></li>
</shiro:hasPermission>
<shiro:hasPermission name="user:update:*">
<li><a href="">修改</a></li>
</shiro:hasPermission>
<shiro:hasPermission name="order:find:*">
<li><a href="">查询</a></li>
</shiro:hasPermission>
</ul>
</li>
</shiro:hasAnyRoles>
<hr>
<shiro:hasRole name="admin">
<li><a href="">用户管理(满足admin权限)</a>
<ul>
<li><a href="">商品管理</a></li>
<li><a href="">订单管理</a></li>
<li><a href="">物流管理</a></li>
</ul>
</shiro:hasRole>
</body>
</html>
6、测试
使用xiaochen登录,xioachen具有admin角色,admin角色具有:user:*:* product:*:01 order:*:*权限
使用zhangsan登录,zhangsan具有user、product角色。
user角色具有:user:*:*
product角色具有:product:*:01 权限
三、Shiro整合Cache
3.1、整合Ehcache
引入依赖
<!--Shiro整合Ehcache-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.5.3</version>
</dependency>
修改自定义Realm
/**
* 创建自定义realm
* @return 自定义Realm
*/
@Bean("realm")
public Realm getRealm(){
CustomerRealm customerRealm = new CustomerRealm();
//设置hashed凭证匹配器
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
//设置md5加密
credentialsMatcher.setHashAlgorithmName("md5");
//设置散列次数
credentialsMatcher.setHashIterations(1024);
customerRealm.setCredentialsMatcher(credentialsMatcher);
//开启缓存管理
customerRealm.setCacheManager(new EhCacheManager());
customerRealm.setCachingEnabled(true);//开启全局缓存
customerRealm.setAuthenticationCachingEnabled(true);//认证认证缓存
customerRealm.setAuthenticationCacheName("authenticationCache");
customerRealm.setAuthorizationCachingEnabled(true);//开启授权缓存
customerRealm.setAuthorizationCacheName("authorizationCache");
return customerRealm;
}
测试
在第一次加载的时候会加载权限认证授权等数据,后序不会加载。
3.2、整合Redis
1、导入SpringBoot整合Redis依赖
<!--redis整合springboot-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2、配置application.properties
##################Redis配置#############
spring.redis.port=6379
spring.redis.host=localhost
spring.redis.database=2
3、SpringBoot获取Bean工具类
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
* @author zhixi
*/
@Component
public class ApplicationContextUtils implements ApplicationContextAware {
private static ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
context = applicationContext;
}
/**
* 根据bean名字获取工厂中指定bean 对象
*
* @param beanName bean名称
* @return bean对象
*/
public static Object getBean(String beanName) {
return context.getBean(beanName);
}
}
4、创建Redis缓存管理器
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.cache.CacheManager;
/**
* @author zhixi
*/
public class RedisCacheManager implements CacheManager {
/**
* 缓存
*
* @param cacheName 认证或者是授权缓存的统一名称
* @param <K> k
* @param <V> v
* @return 自定义CacheManager
* @throws CacheException 异常
*/
@Override
public <K, V> Cache<K, V> getCache(String cacheName) throws CacheException {
System.out.println(cacheName);
return new RedisCache<K, V>(cacheName);
}
}
5、Redis缓存实现类
/**
* 自定义redis缓存的实现
*
* @param <k> k
* @param <v> v
*/
@SuppressWarnings("all")
public class RedisCache<k, v> implements Cache<k, v> {
private String cacheName;
public RedisCache() {
}
public RedisCache(String cacheName) {
this.cacheName = cacheName;
}
@Override
public v get(k k) throws CacheException {
v v = (v) getRedisTemplate().opsForHash().get(this.cacheName, k.toString());
return v;
}
@Override
public v put(k k, v v) throws CacheException {
System.out.println("put key: " + k);
System.out.println("put value:" + v);
getRedisTemplate().opsForHash().put(this.cacheName, k.toString(), v);
return null;
}
@Override
public v remove(k k) throws CacheException {
System.out.println("=============remove=============");
return (v) getRedisTemplate().opsForHash().delete(this.cacheName, k.toString());
}
@Override
public void clear() throws CacheException {
System.out.println("=============clear==============");
getRedisTemplate().delete(this.cacheName);
}
@Override
public int size() {
return getRedisTemplate().opsForHash().size(this.cacheName).intValue();
}
@Override
public Set<k> keys() {
return getRedisTemplate().opsForHash().keys(this.cacheName);
}
@Override
public Collection<v> values() {
return getRedisTemplate().opsForHash().values(this.cacheName);
}
private RedisTemplate getRedisTemplate() {
RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
/*设置key的序列化方式*/
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
return redisTemplate;
}
}
6、设置Shiro盐的序列化方式
此处需要在认证的时候设置盐的序列化方式,不能够使用之前的ByteSource.Util了,否则会出现登录(序列化)正常,第二次登录(反序列化)失败的错误
import org.apache.shiro.codec.Base64;
import org.apache.shiro.codec.CodecSupport;
import org.apache.shiro.codec.Hex;
import org.apache.shiro.util.ByteSource;
import java.io.File;
import java.io.InputStream;
import java.io.Serializable;
import java.util.Arrays;
/**
* 解决:
* shiro 使用缓存时出现:java.io.NotSerializableException: org.apache.shiro.util.SimpleByteSource
* no valid constructor
* 序列化后,无法反序列化的问题(https://blog.csdn.net/qq_15090197/article/details/108743271)
*/
public class MySimpleByteSource implements ByteSource, Serializable {
private static final long serialVersionUID = 1L;
private byte[] bytes;
private String cachedHex;
private String cachedBase64;
public MySimpleByteSource(){
}
public MySimpleByteSource(byte[] bytes) {
this.bytes = bytes;
}
public MySimpleByteSource(char[] chars) {
this.bytes = CodecSupport.toBytes(chars);
}
public MySimpleByteSource(String string) {
this.bytes = CodecSupport.toBytes(string);
}
public MySimpleByteSource(ByteSource source) {
this.bytes = source.getBytes();
}
public MySimpleByteSource(File file) {
this.bytes = (new MySimpleByteSource.BytesHelper()).getBytes(file);
}
public MySimpleByteSource(InputStream stream) {
this.bytes = (new MySimpleByteSource.BytesHelper()).getBytes(stream);
}
public static boolean isCompatible(Object o) {
return o instanceof byte[] || o instanceof char[] || o instanceof String || o instanceof ByteSource || o instanceof File || o instanceof InputStream;
}
public void setBytes(byte[] bytes) {
this.bytes = bytes;
}
@Override
public byte[] getBytes() {
return this.bytes;
}
@Override
public String toHex() {
if(this.cachedHex == null) {
this.cachedHex = Hex.encodeToString(this.getBytes());
}
return this.cachedHex;
}
@Override
public String toBase64() {
if(this.cachedBase64 == null) {
this.cachedBase64 = Base64.encodeToString(this.getBytes());
}
return this.cachedBase64;
}
@Override
public boolean isEmpty() {
return this.bytes == null || this.bytes.length == 0;
}
@Override
public String toString() {
return this.toBase64();
}
@Override
public int hashCode() {
return this.bytes != null && this.bytes.length != 0? Arrays.hashCode(this.bytes):0;
}
@Override
public boolean equals(Object o) {
if(o == this) {
return true;
} else if(o instanceof ByteSource) {
ByteSource bs = (ByteSource)o;
return Arrays.equals(this.getBytes(), bs.getBytes());
} else {
return false;
}
}
private static final class BytesHelper extends CodecSupport {
private BytesHelper() {
}
public byte[] getBytes(File file) {
return this.toBytes(file);
}
public byte[] getBytes(InputStream stream) {
return this.toBytes(stream);
}
}
}
7、设置自定义Realm的管理器为redis
8、测试
四、SpringBoot整合验证码功能(非shiro)
1、验证码工具类
View Code
2、添加验证码请求以及修改登录请求
/**
* 用来处理身份认证
*
* @return 登录成功返回页面
*/
@RequestMapping(value = "login", method = RequestMethod.POST)
public String login(User user, String code, HttpSession session) {
//比较验证码
String codes = session.getAttribute("code").toString();
try {
if (codes.equalsIgnoreCase(code)) {
//获取主体对象
Subject subject = SecurityUtils.getSubject();
subject.login(new UsernamePasswordToken(user.getUsername(), user.getPassword()));
return "redirect:/index.jsp";
}else{
throw new RuntimeException("验证码错误!");
}
} catch (UnknownAccountException e) {
e.printStackTrace();
System.out.println("用户名错误!");
} catch (IncorrectCredentialsException e) {
e.printStackTrace();
System.out.println("密码错误!");
} catch (Exception e) {
e.printStackTrace();
System.out.println("验证码错误");
}
return "redirect:/login.jsp";
}
3、登录页面添加验证码标签
<%@page cnotallow="text/html; UTF-8" pageEncoding="UTF-8" isELIgnored="false" %>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
cnotallow="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" cnotallow="ie=edge">
<title>Document</title>
</head>
<body>
<h1>登录页面</h1>
<form actinotallow="${pageContext.request.contextPath}/user/login" method="post">
<label>
用户名:
<input type="text" name="username">
</label> <br/>
<label>
密码 :
<input type="text" name="password">
</label> <br>
<label>
输入验证码:<input type="text" name="code">
<img src="${pageContext.request.contextPath}/user/getImage" alt="验证码"/>
</label><br>
<input type="submit" value="登录"> <a href="/register.jsp">没有账号?点我注册</a>
</form>
</body>
</html>
4、Shiro放行验证码请求
5、测试
五、Shiro整合Thymeleaf
https://github.com/zhangzhixi0305/springboot-study-shiro/tree/master/shiro-04-thymeleaf