Shiro
Java安全框架
1.什么是权限管理
基本上涉及到用户参与的系统都要进行权限管理,权限管理属于系统安全的范畴,权限管理实现对用户访问系统的控制,按照安全规则或者安全策略控制用户可以访问而且只能访问自己被授权的资源。
权限管理包括用户身份认证和授权两部分,简称认证授权。对于需要访问控制的资源用户首先经过身份认证,认证通过后用户具有该资源的访问权限方可访问。
2.认证
2.1.什么是认证
用户访问系统的控制 控制该用户能不能访问我们的系统
2.2.认证抽象出的对象
-
Subject:主体
-
Principal:身份信息
是主体(subject)进行身份认证的标识,标识必须具有唯一性,如用户名、手机号、邮箱地址等,一个主体可以有多个身份,但是必须有一个主身份(Primary Principal)。
- credential:凭证信息
是只有主体自己知道的安全信息,如密码、证书等。
- token:令牌 Token=身份信息+凭证信息
3.授权
3.1.什么是授权
授权,即访问控制,控制谁能访问哪些资源。主体进行身份认证后需要分配权限方可访问系统的资源,对于某些资源没有权限是无法访问的。
3.2.授权抽象出的对象
授权可简单理解为who对what(which)进行How操作:
-
Who,即主体(Subject),主体需要访问系统中的资源。
-
What,即资源(Resource),如系统菜单、页面、按钮、类方法、系统商品信息等。资源包括资源类型和资源实例,比如商品信息为资源类型,类型为t01的商品为资源实例,编号为001的商品信息也属于资源实例。
-
How,权限/许可(Permission),规定了主体对资源的操作许可,权限离开资源没有意义,如用户查询权限、用户添加权限、某个类方法的调用权限、编号为001用户的修改权限等,通过权限可知主体对哪些资源都有哪些操作许可。
权限分为粗颗粒和细颗粒,粗颗粒权限是指对资源类型的权限,细颗粒权限是对资源实例的权限。
4.权限模型
认证授权的过程
-
1.用户要认证通过
-
2.授权(用户能看到什么样的页面)
- 2.1 根据用户查询用户对应的角色
- 2.2 根据角色查询角色对 应的权限
- 2.3 根据用户的权限渲染页面
5.权限控制方案
用户拥有了权限即可操作权限范围内的资源,系统不知道主体是否具有访问权限需要对用户的访问进行控制。
5.1 基于角色的访问控制
RBAC基于角色的访问控制(Role-Based Access Control)是以角色为中心进行访问控制,比如:主体的角色为总经理可以查询企业运营报表,查询员工工资信息等,访问控制流程如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WgVx38HP-1674897822167)(img\wps1.png)]
上图中的判断逻辑代码可以理解为:
if(主体.hasRole(“总经理角色id”)){
查询工资
}
缺点:以角色进行访问控制粒度较粗,如果上图中查询工资所需要的角色变化为总经理和部门经理,此时就需要修改判断逻辑为“判断主体的角色是否是总经理或部门经理”,系统可扩展性差。
修改代码如下:
if(主体.hasRole(“总经理角色id”) || 主体.hasRole(“部门经理角色id”)){
查询工资
}
5.2 基于资源(权限)的访问控制
RBAC基于资源的访问控制(Resource-Based Access Control)是以资源为中心进行访问控制,比如:主体必须具有查询工资权限才可以查询员工工资信息等,访问控制流程如下:
上图中的判断逻辑代码可以理解为:
if(主体.hasPermission(“查询工资权限标识”)){
查询工资
}
优点:系统设计时定义好查询工资的权限标识,即使查询工资所需要的角色变化为总经理和部门经理也只需要将“查询工资信息权限”添加到“部门经理角色”的权限列表中,判断逻辑不用修改,系统可扩展性强。
6.什么是Shiro
Shiro是apache旗下一个开源框架,它将软件系统的安全认证相关的功能抽取出来,实现用户身份认证,权限授权、加密、会话管理等功能,组成了一个通用的安全认证框架。
Apache Shiro™ is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management. With Shiro’s easy-to-understand API, you can quickly and easily secure any application – from the smallest mobile applications to the largest web and enterprise applications.
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
7.为什么要学shiro
既然shiro将安全认证相关的功能抽取出来组成一个框架,使用shiro就可以非常快速的完成认证、授权等功能的开发,降低系统成本。
shiro使用广泛,shiro可以运行在web应用,非web应用,集群分布式应用中越来越多的用户开始使用shiro。
java领域中spring security(原名Acegi)也是一个开源的权限管理框架,但是spring security依赖spring运行,而shiro就相对独立,最主要是因为shiro使用简单、灵活,所以现在越来越多的用户选择shiro。
8.Shiro核心架构图
9.开发Shiro第一个认证程序
9.1.导入jar包
<dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-core</artifactId><version>1.3.2</version>
</dependency>
8.2.配置Shiro的配置文件
配置文件必须是以.ini结尾的文件
[users]
xiaohei=123456
xiaohuang=111111
9.3.开发第一个认证程序
//后台认证方法public static void testlogin(String username,String password) {//初始化安全管理器工厂IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini");//根据安全管理器工厂初始化安全管理器SecurityManager securityManager = factory.createInstance();//将安全管理器交给安全工具类SecurityUtils.setSecurityManager(securityManager);//根据安全工具类获取主体对象Subject subject = SecurityUtils.getSubject();//创建令牌 token=身份信息(username)+凭证信息(password)AuthenticationToken token=new UsernamePasswordToken(username,password);//认证try {subject.login(token);} catch (UnknownAccountException e) {System.out.println("未知的账号异常 用户名不正确");}catch (IncorrectCredentialsException e) {System.out.println("不正确的凭证异常 密码错误");}/** UnknownAccountException: 未知的账号异常 用户名不正确* IncorrectCredentialsException:不正确的凭证异常 密码错误* */boolean authenticated = subject.isAuthenticated();System.out.println("认证状态: "+authenticated);}public static void main(String[] args) {testlogin("xiaohei","123456");}
注意:
shiro是通过抛异常的形式告诉我们是否认证成功,一般会抛出以下连个异常:
-
UnknownAccountException: 未知的账号异常 用户名不正确
-
IncorrectCredentialsException:不正确的凭证异常 密码错误
-
更多如下:
DisabledAccountException(帐号被禁用)
LockedAccountException(帐号被锁定)
ExcessiveAttemptsException(登录失败次数过多)
ExpiredCredentialsException(凭证过期)等
10.源码中的关键类
//抽象类
abstract class AuthenticatingRealm//凭证匹配器 比对凭证信息的 默认SimpleCredentialsMatcher 做凭证比对
private CredentialsMatcher credentialsMatcher;//抽象方法
protected abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException;
//实现类
public class SimpleAccountRealm extends AuthorizingRealm extends AuthenticatingRealm//实现抽象方法
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {//强转UsernamePasswordToken upToken = (UsernamePasswordToken) token;//upToken.getUsername() 获取用户名//getUser User user=queryByUsername(username)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 SimpleAccount getUser(String username) {//加读锁USERS_LOCK.readLock().lock();try {//this.users = new LinkedHashMap<String, SimpleAccount>();//0 xiaohei 123456//1 xiaohuang 111111//根据用户名从Map中取出键值对(封装成一个对象)return this.users.get(username);} finally {USERS_LOCK.readLock().unlock(); //解锁}
}
//默认凭证匹配器
SimpleCredentialsMatcher //比对密码 equalspublic boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {//从token中获取凭证信息Object tokenCredentials = getCredentials(token);//从info中获取凭证信息Object accountCredentials = getCredentials(info);return equals(tokenCredentials, accountCredentials);
}
11.认证连接数据库
1.自定义一个Realm类
extends AuthenticatingRealm抽象类,实现doGetAuthenticationInfo抽象方法
public class MyRealm extends AuthenticatingRealm {//认证@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {System.out.println("----");//强转/*UsernamePasswordToken upToken = (UsernamePasswordToken) token;String principals = (String) upToken.getPrincipal();String usernames = upToken.getUsername();*///强转String usename = (String) token.getPrincipal();//查询数据库//User u=queryByUsername(username)//new User("1","xiaohei","123456")//SimpleAccount simpleAccount = new SimpleAccount();AuthenticationInfo info =null;if(usename.equals("xiaohei")){//根据数据库查询的数据封装info对象info =new SimpleAuthenticationInfo("xiaohei","123456",this.getName());}return info;}}
2.配置shiro的配置文件
[main]#配置自定义Realm
myrealm=com.baizhi.realm.MyRealm#将自定义realm交给凭证匹配器
securityManager.realms=$myrealm
12.Shiro加密认证
12.1加密
shiro中支持的加密算法有:MD2,MD5,SHA1,SHA256,SHA384,SHA512
public static void main(String[] args) {//MD5 SHA//创建加密算法 参数:明文密码//Md5Hash md5Hash = new Md5Hash("111111");//创建加密算法 参数:明文密码,随机盐//Md5Hash md5Hash = new Md5Hash("111111","abcd");//创建加密算法 参数:明文密码,随机盐,散列次数//Md5Hash md5Hash = new Md5Hash("111111","abcd",1024);Md2Hash md2Hash = new Md2Hash("111111");Sha1Hash sha1Hash = new Sha1Hash("111111");Sha256Hash sha256Hash = new Sha256Hash("111111");Sha384Hash sha384Hash = new Sha384Hash("111111");Sha512Hash sha512Hash = new Sha512Hash("111111");//加密String str = md2Hash.toHex();String str1 = sha1Hash.toHex();String str2 = sha256Hash.toHex();String str3 = sha384Hash.toHex();String str4 = sha512Hash.toHex();System.out.println(str);System.out.println(str1);System.out.println(str2);System.out.println(str3);System.out.println(str4);/** 96e79218965eb72c92a549dd5a330112 111111* f1293bcae648b20fb3329c21b9af1638 abcd 111111* c3f2b09474f65a0bb8eda78e3682955f abcd 111111 1024* *//*Md5Hash: 96e79218 965eb72c 92a549dd 5a330112 32位16进制数md2Hash: 43f44d2e 244d26cc e9272c71 b28138dcsha1Hash: 3d4f2bf0 7dc1be38 b20cd6e4 6949a107 1f9d0e3d 40sha256Hash: bcb15f82 1479b4d5 772bd0ca 866c00ad 5f926e35 80720659 cc80d39c 9d09802a 64sha384Hash: 1b0268a 40ae44c012946c974d60bf5291e7bb7c63cdb72a904d9283e3dc0a34de9afebe4035665768aaa503a4e7a30c3 128sha512Hash: b0412597dcea813655574dc54a5b74967cf85317f0332a2591be7953a016f8de56200eb37d5ba593b1e4aa27cea5ca27100f94dccd5b04bae5cadd4454dba67d 156** */}
12.2配置加密算法
#方案一:
#配置凭证匹配器
credentialsMatcher=org.apache.shiro.authc.credential.Md5CredentialsMatcher#设置散列次数credentialsMatcher.hashIterations=1024#方案二:
#配置凭证匹配器
credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher#设置散列次数credentialsMatcher.hashIterations=1024#设置加密算法credentialsMatcher.hashAlgorithmName=MD5#将凭证匹配器交给自定义Realm
myrealm.credentialsMatcher=$credentialsMatcher
12.3.加密认证
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {System.out.println("----");//强转UsernamePasswordToken upToken = (UsernamePasswordToken) token;//获取用户名String username = upToken.getUsername();//查询数据库//User u=queryByUsername(username)//new User("1","xiaohei","c3f2b09474f65a0bb8eda78e3682955f","abcd")AuthenticationInfo info =null;if(username.equals("xiaohei")){//根据数据库查询的数据封装info对象 info =new SimpleAuthenticationInfo("xiaohei", //用户名"c3f2b09474f65a0bb8eda78e3682955f", //密文密码ByteSource.Util.bytes("abcd"), //随机盐this.getName());}return info;
}
13.授权
13.1实现授权方法
extends AuthorizingRealm抽象类,实现doGetAuthorizationInfo抽象方法
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {//获取用户主身份String username = (String) principals.getPrimaryPrincipal();//数据库查询//根据用户名插叙该用户有哪些角色 角色集合 admin sAdmin//根据角色查询用户有哪些权限 权限集合// admin(user:query user:update category:query category:delete category:update category:add)// sAdmin(admin:query admin:add admin:delete log:query)//admin,sAdmin//用户管理// user:query user:update//类别管理//视频管理//sAdmin//管理员管理// admin:query//财务//查询工资//老板//扣工资SimpleAuthorizationInfo info=null;if(username.equals("xiaohei")){//设置权限配置info=new SimpleAuthorizationInfo();//给当前主体赋予一个角色//info.addRole("admin");//给当前主体赋予多个角色info.addRoles(Arrays.asList("admin","sAdmin"));//给当前主体赋予一个权限//info.addStringPermission("user:query");//给当前主体赋予多个权限info.addStringPermissions(Arrays.asList("user:query","user:update","admin:query","admin:add","log:query"));}return info;
}
13.2.测试授权
//后台认证方法public static void testlogin(String username,String password) {//初始化安全管理器工厂IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini");//根据安全管理器工厂初始化安全管理器SecurityManager securityManager = factory.createInstance();//将安全管理器交给安全工具类SecurityUtils.setSecurityManager(securityManager);//根据安全工具类获取主体对象Subject subject = SecurityUtils.getSubject();//创建令牌 token=身份信息(username)+凭证信息(password)AuthenticationToken token=new UsernamePasswordToken(username,password);//认证try {subject.login(token);}catch (UnknownAccountException e) {System.out.println("未知的账号异常 用户名不正确");}catch (IncorrectCredentialsException e) {System.out.println("不正确的凭证异常 密码错误");}/** UnknownAccountException: 未知的账号异常 用户名不正确* IncorrectCredentialsException:不正确的凭证异常 密码错误* *///判断是否认证成功boolean authenticated = subject.isAuthenticated();System.out.println("认证状态: "+authenticated);if(authenticated){//获取角色 渲染页面//判断当前主体是否有该角色//boolean b = subject.hasRole("admin");//判断当前猪体是否含有这些角色//boolean[] booleans = subject.hasRoles(Arrays.asList("admin", "sAdmin", "super"));//判断当前主体是否有这些所有的角色boolean b = subject.hasAllRoles(Arrays.asList("admin", "sAdmin"));System.out.println("角色授权状态: "+b);//判断当前主体是否有该权限//boolean permitted = subject.isPermitted("user:query");//判断当前主体是否有这些权限//boolean[] permitteds = subject.isPermitted("user:query", "admin:add");//判断当前主体是否有这些所有权限boolean permitted = subject.isPermittedAll("user:query", "admin:add");System.out.println("权限授权状态: "+permitted);}}public static void main(String[] args) {testlogin("xiaohei","111111");//xiaohei 123456}
13.3.权限控制方案
基于角色的权限控制
//判断当前主体是否有该角色
boolean b = subject.hasRole("admin");
//判断当前猪体是否含有这些角色
boolean[] booleans = subject.hasRoles(Arrays.asList("admin", "sAdmin", "super"));
//判断当前主体是否有这些所有的角色
boolean b = subject.hasAllRoles(Arrays.asList("admin", "sAdmin"));
基于权限的权限控制
//判断当前主体是否有该权限
boolean permitted = subject.isPermitted("user:query");
//判断当前主体是否有这些权限
boolean[] permitteds = subject.isPermitted("user:query", "admin:add");
//判断当前主体是否有这些所有权限
boolean permitted = subject.isPermittedAll("user:query", "admin:add");
13.4.权限字符串
权限字符串的规则是:“资源标识符:操作:资源实例标识符”,意思是对哪个资源的哪个实例具有什么操作,“:”是资源/操作/实例的分割符,权限字符串也可以使用*通配符。
例子:
用户创建权限:user:create,或user:create:*
用户修改实例001的权限:user:update:001
用户实例001的所有权限:user:*:001
14.Shiro集成SpringBoot
14.1.创建一个springBoot项目
14.2.导入Shiro依赖
springBoot依赖
<!--web支持的jar springboot的启动器-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency><!--测试支持的jar-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><!-- 只在test测试里面运行 --><scope>test</scope>
</dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.4</version><scope>provided</scope>
</dependency><!-- 给内嵌tomcat提供jsp解析功能的jar-->
<dependency><groupId>org.apache.tomcat.embed</groupId><artifactId>tomcat-embed-jasper</artifactId>
</dependency><dependency><groupId>jstl</groupId><artifactId>jstl</artifactId><version>1.2</version>
</dependency>Shiro依赖
<dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-core</artifactId><version>1.7.0</version>
</dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-web</artifactId><version>1.7.0</version>
</dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>1.7.0</version>
</dependency>
14.3.配置项目配置
server:port: 9191servlet:context-path: /shirojsp:init-parameters:development: true
spring:mvc:view:prefix: /suffix: .jsp
14.4.配置ShiroFilter配置
@Configuration
public class Myconf {//将Shiro过滤器工厂对象交给Spring工厂管理@Beanpublic ShiroFilterFactoryBean getShiroFilter(DefaultWebSecurityManager securityManager){//创建Shiro过滤器工厂对象ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();//将安全管理器对象交给过滤器工厂factoryBean.setSecurityManager(securityManager);//设置过滤规则的mapHashMap<String, String> map = new HashMap<>();/** anon 匿名过滤器 配置的资源不用认证就可以访问* authc 认证过滤器 配置的所有资源必须要认证通过才能访问* *///资源路径,过滤器简称map.put("/**","authc");map.put("/user/login","anon");//配置过滤器链(多个过滤器)factoryBean.setFilterChainDefinitionMap(map);//指定登陆页面的位置factoryBean.setLoginUrl("/user/login.jsp");return factoryBean;}//将安全管理器对象交给Spring工厂管理@Beanpublic DefaultWebSecurityManager getDefaultWebSecurityManager(){//创建安全管理器对象DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();return securityManager;}
}
14.5.Shiro的过滤器
过滤器简称 | 对应的java类 |
---|---|
anon | org.apache.shiro.web.filter.authc.AnonymousFilter |
authc | org.apache.shiro.web.filter.authc.FormAuthenticationFilter |
authcBasic | org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter |
perms | org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter |
port | org.apache.shiro.web.filter.authz.PortFilter |
rest | org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter |
roles | org.apache.shiro.web.filter.authz.RolesAuthorizationFilter |
ssl | org.apache.shiro.web.filter.authz.SslFilter |
user | org.apache.shiro.web.filter.authc.UserFilter |
logout | org.apache.shiro.web.filter.authc.LogoutFilter |
注意:
anon,authcBasic,auchc,user是认证过滤器,
perms,roles,ssl,rest,port是授权过滤器
14.6.认证
1.登陆页面
<%@page pageEncoding="UTF-8" isELIgnored="false" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<c:set var="path" value="${pageContext.request.contextPath}"/><!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><div align="center"><div style="border: 3px #cad solid;width: 300px;height: 200px" ><form method="post" action="${path}/user/login"><br><br>用户名:<input type="text" name="username" /><br><br>密 码:<input type="password" name="password"/><br><br><input type="submit" value="登陆"/><br><br></form></div></div></body>
</html>
2.登陆方法
@RequestMapping("login")
public String login(String username,String password){@RequestMapping("login")public String login(String username,String password){System.out.println("username: "+username);//获取主体对象Subject subject = SecurityUtils.getSubject();//封装tokenUsernamePasswordToken token = new UsernamePasswordToken(username, password);String message=null;try {//认证subject.login(token);return "redirect:/main/main.jsp";}catch (UnknownAccountException e){message="用户名不正确";System.out.println("认证结果: "+message);return "redirect:/user/login.jsp";}catch (IncorrectCredentialsException e){message="密码不正确";System.out.println("认证结果: "+message);return "redirect:/user/login.jsp";}}
}
3.主体认证
自定义一个Realm类,继承AuthorizingRealm实现doGetAuthenticationInfo方法
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {String username = (String) token.getPrincipal();//User("1","xiaohei","c3f2b09474f65a0bb8eda78e3682955f","abcd")AuthenticationInfo info =null;if(username.equals("xiaohei")){info =new SimpleAuthenticationInfo(username,"c3f2b09474f65a0bb8eda78e3682955f",ByteSource.Util.bytes("abcd"),this.getName());}return info;
}
4.配置认证相关配置
-
1.自定义Relam
-
2.将自定义Realm交给安全管理器
-
3.配置凭证匹配器 设置加密算法 散列次数
-
4.将凭证匹配器交给自定义Realm
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(MyRealm myRealm){//创建安全管理器对象DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();//将自定义Realm交给安全管理器securityManager.setRealm(myRealm);return securityManager;
}//将自定义Realm对象交给Spring工厂管理
@Bean
public MyRealm getMyRealm(HashedCredentialsMatcher credentialsMatcher){//创建自定义的Realm对象MyRealm myRealm = new MyRealm();//将凭证匹配器给自定义的RealmmyRealm.setCredentialsMatcher(credentialsMatcher);return myRealm;
}//将凭证匹配器对象交给Spring工厂管理
@Bean
public HashedCredentialsMatcher getHashedCredentialsMatcher(){//创建凭证匹配器HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();credentialsMatcher.setHashAlgorithmName("MD5"); //加密算法credentialsMatcher.setHashIterations(1024); //散列次数return credentialsMatcher;
}
5.退出
5.1.退出页面
<a href="${path}/user/logout">退出</a>
5.2.退出程序
@RequestMapping("logout")
public String logout(){//获取主体对象Subject subject = SecurityUtils.getSubject();//调用退出登录方法subject.logout();return "redirect:/user/login.jsp";
}
5.3.使用退出过滤器退出
<a href="${path}/logout">退出</a>
//配置退出过滤器 具体的退出代码Shiro已经实现
map.put("/logout","logout");
14.7.Shiro中的标签
导入Shiro的标签库
<%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
Shiro中的标签
<!--获取身份信息-->
<shiro:principal/><!--认证之后展示的内容-->
<!--记住我登陆不算是认证通过 没有调用subject.login(token)-->
<shiro:authenticated></shiro:authenticated>
<!--没有认证展示的内容-->
<shiro:notAuthenticated></shiro:notAuthenticated><!--认证成功之后展示的内容/记住我登陆-->
<shiro:user></shiro:user>
<!--没有认证成功之后展示的内容 游客访问到的内容-->
<shiro:guest></shiro:guest>
14.9 配置Shiro中Session 的过期时间
在shiro中自带SessionManager ,session默认过期时间时30分钟
设置过期时间
//将Session管理器对象交给Spring工厂管理
@Bean
public DefaultWebSessionManager getDefaultWebSessionManager(){//创建Session管理器对象DefaultWebSessionManager sessionManager=new DefaultWebSessionManager();//设置session过期时间 参数:long类型的过期时间单位时毫秒sessionManager.setGlobalSessionTimeout((1*1000)*60*2);return sessionManager;
}
将Session管理器交给安全管理器
//将Session管理器交给安全管理器
securityManager.setSessionManager(sessionManager);
14.10.记住我的登陆
1.页面加入记住我复选框
<form action="${path}/user/login" method="post"/><br><br>用户名:<input name="username" type="text"/><br><br>密 码:<input name="password" type="password"/><br><br><input type="checkbox" name="rememberme" value="1">记住我7天</input><br><br><input type="submit" value="点我登陆"/>
</form>
2.开启记住我
@RequestMapping("login")
public String login(String username,String password,Integer rememberme){System.out.println("==rememberme "+rememberme);//根据安全工具类获取主体对象Subject subject = SecurityUtils.getSubject();//创建token对象 参数:身份信息,凭证信息UsernamePasswordToken token = new UsernamePasswordToken(username, password);//判断是否要记住我登陆if(rememberme!=null && rememberme==1){System.out.println("====");//是否开启记住我token.setRememberMe(true);}//认证try {subject.login(token);boolean authenticated = subject.isAuthenticated();System.out.println("认证状态:"+authenticated);return "redirect:/main/main.jsp";} catch (UnknownAccountException e) {System.out.println("未知的账户异常 用户名不正确");return "redirect:/login/login.jsp";}catch (IncorrectCredentialsException e){System.out.println("不正确的凭证异常 密码错误");return "redirect:/login/login.jsp";}
}
3.配置记住我过期时间
//将记住我管理器交给Spring工厂管理
@Bean
public CookieRememberMeManager getCookieRememberMeManager(SimpleCookie cookie){//创建记住我管理器CookieRememberMeManager rememberMeManager=new CookieRememberMeManager();//将Cookie 交给记住我管理器rememberMeManager.setCookie(cookie);return rememberMeManager;
}//将Shiro中的Cookie对象交给Spring工厂管理
@Bean
public SimpleCookie getSimpleCookie(){SimpleCookie cookie =new SimpleCookie();//cookie的名称,对应的是前端checkbox中name属性的值 name="rememberme"cookie.setName("rememberme");//设置记住我cookie的失效时间 参数:秒cookie.setMaxAge(60*2);return cookie;
}
将记住我管理器交给安全管理器
//将记住我管理器交给安全管理器
securityManager.setRememberMeManager(rememberMeManager);
4.配置记住我过滤器
map.put("/**","user"); //记住我过滤器,记住我登陆就可以访问
14.11.授权
14.11.1实现授权方法
继承AuthorizingRealm实现doGetAuthorizationInfo方法
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {//获取主身份String username = (String)principalCollection.getPrimaryPrincipal();//根据身份信息查询角色 角色集合//根据角色查询权限 权限集合// admin(user:query user:update category:query category:delete category:update category:add)// sAdmin(admin:query admin:add admin:delete log:query)SimpleAuthorizationInfo info=null;if(username.equals("xiaohei")){//设置主体的角色和权限info=new SimpleAuthorizationInfo();//设置角色info.addRoles(Arrays.asList("admin","sAdmin"));//设置权限info.addStringPermissions(Arrays.asList("user:query","user:update","admin:query","admin:add"));}return info;
}
授权的标签
<!--判断当前主体有这些其中一个角色就展示-->
<shiro:hasAnyRoles name="admin,sAdmin"></shiro:hasAnyRoles>
<!--判断当前主体是否由该角色-->
<shiro:hasRole name="sAdmin"></shiro:hasRole>
<!--判断当前主体没有该角色-->
<shiro:lacksRole name="super"></shiro:lacksRole>
<!--判断当前主体没有有该权限时展示-->
<shiro:lacksPermission name=""></shiro:lacksPermission>
<!--判断当前主体有该权限时展示-->
<shiro:hasPermission name="user:add"></shiro:hasPermission>
配置授权过滤器
//角色过滤器
map.put("/user/add","roles[userA]");
//权限过滤器
map.put("/user/add","perms[user:add]");
14.12.缓存
1.导入jar包
<dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-ehcache</artifactId><version>1.7.0</version>
</dependency>
2.配置缓存
//将缓存交给Spring工厂
@Bean
public CacheManager getCacheManager(){//1.配置缓存CacheManager cacheManager = new EhCacheManager();//2.将缓存配置给myRealm 注意:在Realm中配置myRealm.setCacheManager(cacheManager);return cacheManager;
}
14.13.示例
1.配置
package com.baizhi.conf;import com.baizhi.realm.MyRealm;
import com.sun.org.apache.bcel.internal.generic.NEW;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.cache.ehcache.EhCacheManager;
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;import javax.annotation.Resource;
import java.util.HashMap;@Configuration
public class ShiroFilterConf {//将shiro过滤器工厂交给Spring工厂@Beanpublic ShiroFilterFactoryBean getShiroFilterFactoryBean(SecurityManager securityManager){//创建shiro过滤器工厂ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();//将安全管理器交给shiro过滤器工厂shiroFilterFactoryBean.setSecurityManager(securityManager);HashMap<String, String> map = new HashMap<>();//FormAuthenticationFilter authc 认证过滤器//AnonymousFilter anon 匿名过滤器map.put("/**","authc");map.put("/user/login","anon");map.put("/main/main.jsp","anon");//指定拦截跳转的login页面shiroFilterFactoryBean.setLoginUrl("/main/login.jsp");//定义一个过滤器链shiroFilterFactoryBean.setFilterChainDefinitionMap(map);return shiroFilterFactoryBean;}//将安全管理器交给Spring工厂@Beanpublic SecurityManager getSecurityManager(MyRealm myRealm){//创建安全管理器DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();//将自定义Realm配置到安全管理器中securityManager.setRealm(myRealm);return securityManager;}//将自定义Realm交给Spring工厂@Beanpublic MyRealm getMyRealm(HashedCredentialsMatcher credentialsMatcher,CacheManager cacheManager){MyRealm myRealm = new MyRealm();//配置缓存myRealm.setCacheManager(cacheManager);//将凭证匹配器交给自定义RealmmyRealm.setCredentialsMatcher(credentialsMatcher);return myRealm;}//将凭证匹配器交给Spring工厂@Beanpublic HashedCredentialsMatcher getHashedCredentialsMatcher(){//创建凭证匹配器HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();//设置使用的加密算法credentialsMatcher.setHashAlgorithmName("MD5");//设置散列次数credentialsMatcher.setHashIterations(1024);return credentialsMatcher;}//将缓存交给Spring工厂@Beanpublic CacheManager getCacheManager(){//配置缓存CacheManager cacheManager = new EhCacheManager();return cacheManager;}
}
2.自定义Realm认证授权
package com.baizhi.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;public class MyRealm extends AuthorizingRealm {//授权@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {System.out.println("===授权===");//获取身份信息String username = (String) principalCollection.getPrimaryPrincipal();//根据身份信息查询 角色 权限//根据角色查权限SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();if(username.equals("xiaohei")){//将查询出来的角色放入授权信息中info.addRoles(Arrays.asList("user","admin"));//将查询出来的权限放入授权信息中info.addStringPermissions(Arrays.asList("user:query","user:update","admin:query"));}return info;}//认证@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {System.out.println("===认证===");//获取身份信息String username = (String) authenticationToken.getPrincipal();//去数据库查 根据身份信息查询用户 省略if(username.equals("xiaohei")){AuthenticationInfo info=new SimpleAuthenticationInfo("xiaohei","c3f2b09474f65a0bb8eda78e3682955f", ByteSource.Util.bytes("abcd"),this.getName());return info;}return null;}
}
3.Controller
package com.baizhi.controller;import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;@RequestMapping("user")
@Controller
public class UserController {@RequestMapping("login")public String login(String username,String password){System.out.println("username: "+username);System.out.println("password: "+password);//根据安全工具类获取主体Subject subject = SecurityUtils.getSubject();//将主体放入token令牌中AuthenticationToken token=new UsernamePasswordToken(username,password);try {//认证subject.login(token);return "redirect:/main/main.jsp";} catch (UnknownAccountException e) {System.out.println("身份认证失败");return "redirect:/main/login.jsp";}catch (IncorrectCredentialsException e) {System.out.println("密码错误");return "redirect:/main/login.jsp";}}@RequestMapping("logout")public String logout(){//根据安全工具类获取主体Subject subject = SecurityUtils.getSubject();//登出subject.logout();System.out.println("退出");return "redirect:/main/login.jsp";}
}
4.jsp
<%@page pageEncoding="UTF-8" contentType="text/html;UTF-8" isELIgnored="false" %>
<%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %><!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>Main</title>
</head>
<body align="center"><h1>欢迎来到主页面</h1><div align="right"><%--认证成功展示内容--%><shiro:authenticated>欢迎来到主页面,您好<strong><span style="color:red"><shiro:principal/></span></strong>,<a href="${pageContext.request.contextPath}/user/logout">退出</a><br><div align="left"><ul><%--判断该主体是否有该角色--%><shiro:hasRole name="user"><li>轮播图管理</li><br><li>专辑管理</li><br><li>文章管理</li><br></shiro:hasRole><%--判断该主体是否有其中一个角色--%><shiro:hasAnyRoles name="admin,superAdmin"><li>用户管理</li><br><%--判断该主体是否有该权限--%><shiro:hasPermission name="user:query"><button >查</button></shiro:hasPermission><br><shiro:hasPermission name="user:add"><button >增</button></shiro:hasPermission><br><shiro:hasPermission name="user:update"><button >改</button></shiro:hasPermission><br><shiro:hasPermission name="user:delete"><button >删</button></shiro:hasPermission><br></shiro:hasAnyRoles><shiro:hasRole name="superAdmin"><li>管理员管理</li><br></shiro:hasRole></ul></div></shiro:authenticated><%--未认证成功展示内容--%><shiro:notAuthenticated>您还没有<a href="${pageContext.request.contextPath}/main/login.jsp">登陆</a>,如果想浏览更多信息请登录</shiro:notAuthenticated></div>
</body>
</html>
14.Shiro连接数据库
1.准备数据库表
/*
Navicat MySQL Data TransferSource Server : MySQL
Source Server Version : 50528
Source Host : localhost:3306
Source Database : shiroTarget Server Type : MYSQL
Target Server Version : 50528
File Encoding : 65001Date: 2019-08-13 16:39:03
*/SET FOREIGN_KEY_CHECKS=0;-- ----------------------------
-- Table structure for s_admin
-- ----------------------------
DROP TABLE IF EXISTS `s_admin`;
CREATE TABLE `s_admin` (`admin_id` varchar(50) NOT NULL DEFAULT '',`username` varchar(50) DEFAULT NULL,`password` varchar(50) DEFAULT NULL,`salt` varchar(50) DEFAULT NULL,PRIMARY KEY (`admin_id`),UNIQUE KEY `s_admin_admin_id_uindex` (`admin_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;-- ----------------------------
-- Records of s_admin
-- ----------------------------
INSERT INTO `s_admin` VALUES ('1', 'nanan', 'a2c9ec06b8c0a2be811dfd47be6e5f82', 'asdfaf');
INSERT INTO `s_admin` VALUES ('2', 'bobo', 'c3f2b09474f65a0bb8eda78e3682955f', 'abcd');-- ----------------------------
-- Table structure for s_admin_role
-- ----------------------------
DROP TABLE IF EXISTS `s_admin_role`;
CREATE TABLE `s_admin_role` (`id` varchar(50) DEFAULT NULL,`admin_id` varchar(50) DEFAULT NULL,`role_id` varchar(50) DEFAULT NULL,UNIQUE KEY `s_admin_role_id_uindex` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;-- ----------------------------
-- Records of s_admin_role
-- ----------------------------
INSERT INTO `s_admin_role` VALUES ('1', '1', '2');
INSERT INTO `s_admin_role` VALUES ('2', '1', '1');
INSERT INTO `s_admin_role` VALUES ('3', '1', '4');
INSERT INTO `s_admin_role` VALUES ('4', '1', '3');-- ----------------------------
-- Table structure for s_authority
-- ----------------------------
DROP TABLE IF EXISTS `s_authority`;
CREATE TABLE `s_authority` (`authority_id` varchar(50) DEFAULT NULL,`authority_name` varchar(50) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;-- ----------------------------
-- Records of s_authority
-- ----------------------------
INSERT INTO `s_authority` VALUES ('1', 'admin:query');
INSERT INTO `s_authority` VALUES ('2', 'admin:delete');
INSERT INTO `s_authority` VALUES ('3', 'admin:update');
INSERT INTO `s_authority` VALUES ('4', 'admin:insert');-- ----------------------------
-- Table structure for s_role
-- ----------------------------
DROP TABLE IF EXISTS `s_role`;
CREATE TABLE `s_role` (`role_id` varchar(50) DEFAULT NULL,`role_name` varchar(50) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;-- ----------------------------
-- Records of s_role
-- ----------------------------
INSERT INTO `s_role` VALUES ('1', 'common');
INSERT INTO `s_role` VALUES ('2', 'super');
INSERT INTO `s_role` VALUES ('3', 'admin');
INSERT INTO `s_role` VALUES ('4', 'user');-- ----------------------------
-- Table structure for s_role_authority
-- ----------------------------
DROP TABLE IF EXISTS `s_role_authority`;
CREATE TABLE `s_role_authority` (`id` varchar(50) DEFAULT NULL,`role_id` varchar(50) DEFAULT NULL,`authority_id` varchar(50) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;-- ----------------------------
-- Records of s_role_authority
-- ----------------------------
INSERT INTO `s_role_authority` VALUES ('1', '2', '1');
INSERT INTO `s_role_authority` VALUES ('2', '2', '2');
INSERT INTO `s_role_authority` VALUES ('3', '2', '3');
INSERT INTO `s_role_authority` VALUES ('4', '2', '4');
2.配置实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Admin {private String admin_id;private String username;private String password;private String salt;//关系属性 角色集合private List<Role> roles;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Role {private String role_id;private String role_name;//关系集合 权限集合private List<Authority> authorities;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Authority {private String authority_id;private String authority_name;
}
2.配置DAO
public interface AdminDao {Admin queryByUsername(String username);Admin queryByUsernames(String username);
}
3.配置Mapper文件
<?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="com.baizhi.dao.AdminDao">
<!--private String admin_id;private String username;private String password;private String salt;private String role_id;private String role_name;private String authority_id;private String authority_name;
--><resultMap id="queryMap" type="Admin"><id column="aadminId" property="admin_id" /><result column="ausername" property="username" /><result column="apassword" property="password" /><result column="asalt" property="salt" /><collection property="roles" javaType="list" ofType="Role"><id column="rroleId" property="role_id" /><result column="rroleName" property="role_name" /><collection property="authorities" javaType="list" ofType="Authority"><id column="auauthorityId" property="authority_id" /><result column="auauthorityName" property="authority_name" /></collection></collection></resultMap><select id="queryByUsernames" resultMap="queryMap">select a.admin_id aadminId ,a.username ausername,a.password apassword,a.salt asalt,r.role_id rroleId,r.role_name rroleName,au.authority_id auauthorityId,au.authority_name auauthorityNamefrom s_admin aleft join s_admin_role ar on a.admin_id=ar.admin_idleft join s_role r on ar.role_id=r.role_idleft join s_role_authority ra on r.role_id=ra.role_idleft join s_authority au on ra.authority_id= au.authority_idwhere username=#{username}</select><select id="queryByUsername" resultType="Admin">select * from s_admin where username=#{username}</select>
</mapper>
4.认证授权连接数据库
package com.baizhi.realm;import com.baizhi.entity.Admin;
import com.baizhi.entity.Authority;
import com.baizhi.entity.Role;
import com.baizhi.service.AdminService;
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;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;public class MyRealm extends AuthorizingRealm {@ResourceAdminService adminService;//授权@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {System.out.println("===授权===");//获取身份信息String username = (String) principalCollection.getPrimaryPrincipal();//创建角色集合ArrayList<String> roles = new ArrayList<>();//创建权限集合ArrayList<String> permissions = new ArrayList<>();//根据身份信息查询 角色 权限//根据角色查权限Admin admin = adminService.queryByUsernames(username);//获取对应主体的角色集合List<Role> roleList = admin.getRoles();//判断角色集合是否为空if(roleList.size()!=0){//遍历角色集合for (Role role : roleList) {//根据角色对象获取角色名称String role_name = role.getRole_name();//将角色名称放入角色集合roles.add(role_name);//根据角色对象获取该角色下的所有权限List<Authority> authorityList = role.getAuthorities();//判断权限集合是否为空if(authorityList.size()!=0){//遍历权限集合for (Authority authority : authorityList) {//根据权限对象获取权限名称String authority_name = authority.getAuthority_name();//将权限名称放入权限集合permissions.add(authority_name);}}}}//遍历封装好的角色集合for (String name : roles) {System.out.println("==角色名称: "+name);}//遍历封装好的权限集合for (String name : permissions) {System.out.println("==权限名称: "+name);}SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();//将查询出来的角色放入授权信息中info.addRoles(roles);//将查询出来的权限放入授权信息中info.addStringPermissions(permissions);return info;}//认证@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {System.out.println("===认证===");//获取身份信息String username = (String) authenticationToken.getPrincipal();//去数据库查 根据身份信息查询用户 省略Admin admin = adminService.queryByUsername(username);AuthenticationInfo info=null;//判断查询到的用户是否为空if(admin!=null){info = new SimpleAuthenticationInfo(admin.getUsername(),admin.getPassword(), ByteSource.Util.bytes(admin.getSalt()),this.getName());}return info;}
}