Shiro
1.权限的管理
什么是权限管理?
基本上涉及到用户参与的系统都要进行权限管理,权限管理属于系统安全的范畴,权限管理实现对用户访问系统的控制,按照安全规则或者安全策略控制用户可以访问而且只能访问自己被授权的资源。
权限管理包括用户身份认证和授权两部分,简称认证授权。对于需要访问控制的资源用户首先经过身份认证,认证通过后用户具有该资源的访问权限方可访问。
2.认证(Authentication)
2.1 认证的概念
用户访问系统的控制
身份认证,就是判断一个用户是否为合法用户的处理过程。最常用的简单身份认证方式是系统通过核对用户输入的用户名和口令,看其是否与系统中存储的该用户的用户名和口令一致,来判断用户身份是否正确。对于采用指纹等系统,则出示指纹;对于硬件Key等刷卡系统,则需要刷卡。
2.2 认证中抽象出的关键对象
-
Subject:主体
-
Principal:身份信息 username
是主体(subject)进行身份认证的标识,标识必须具有唯一性,如用户名、手机号、邮箱地址等,一个主体可以有多个身份,但是必须有一个主身份(Primary Principal)。
- credential:凭证信息 password
是只有主体自己知道的安全信息,如密码、证书等。
- token :令牌 token=身份信息+凭证信息
3.授权(Authorization)
3.1授权概念
授权,即访问控制,控制谁能访问哪些资源。主体进行身份认证后需要分配权限方可访问系统的资源,对于某些资源没有权限是无法访问的。
3.2 授权中抽象出的关键对象
授权可简单理解为who对what(which)进行How操作:
-
Who,即主体(Subject),主体需要访问系统中的资源。
-
What,即资源(Resource),如系统菜单、页面、按钮、类方法、系统商品信息等。资源包括资源类型和资源实例,比如商品信息为资源类型,类型为t01的商品为资源实例,编号为001的商品信息也属于资源实例。
-
How,权限/许可(Permission),规定了主体对资源的操作许可,权限离开资源没有意义,如用户查询权限、用户添加权限、某个类方法的调用权限、编号为001用户的修改权限等,通过权限可知主体对哪些资源都有哪些操作许可。
权限分为粗颗粒和细颗粒,粗颗粒权限是指对资源类型的权限,细颗粒权限是对资源实例的权限。
一般配置的都是粗颗粒
4.权限模型
用户表
用户id 用户名 用户密码
1 xiaohei 111111
2 xiaohuang 2222222
角色表
id 角色名
11 admin
12 superAdmin
13 ssAdmin
用户角色表
id 用户id 角色id
a 1 11
b 1 12
c 2 13
权限表
id 权限标识
22 1: 轮播图管理
23 2:专辑管理
24 3:文章管理
25 4: 用户管理
26 5:管理员管理
27 7:查询工资
资源表
id 资源标识
32 轮播图管理
33 专辑管理
34 文章管理
35 用户管理
36 管理员管理
角色权限表
id 角色id 权限id
80 11 22
81 11 23
5.权限控制方案
用户拥有了权限即可操作权限范围内的资源,系统不知道主体是否具有访问权限需要对用户的访问进行控制。
5.1 基于角色的访问控制
RBAC基于角色的访问控制(Role-Based Access Control)是以角色为中心进行访问控制,比如:主体的角色为总经理可以查询企业运营报表,查询员工工资信息等,访问控制流程如下:
上图中的判断逻辑代码可以理解为:
if(主体.hasRole(“总经理角色id”)){
查询工资
}
缺点:以角色进行访问控制粒度较粗,如果上图中查询工资所需要的角色变化为总经理和部门经理,此时就需要修改判断逻辑为“判断主体的角色是否是总经理或部门经理”,系统可扩展性差。
修改代码如下:
if(主体.hasRole(“总经理角色id”) || 主体.hasRole(“部门经理角色id”)){
查询工资
}
5.1 基于资源的访问控制
RBAC基于资源的访问控制(Resource-Based Access Control)是以资源为中心进行访问控制,比如:主体必须具有查询工资权限才可以查询员工工资信息等,访问控制流程如下:
上图中的判断逻辑代码可以理解为:
if(主体.hasPermission(“查询工资权限标识”)){
查询工资
}
优点:系统设计时定义好查询工资的权限标识,即使查询工资所需要的角色变化为总经理和部门经理也只需要将“查询工资信息权限”添加到“部门经理角色”的权限列表中,判断逻辑不用修改,系统可扩展性强。
基于角色和资源的访问控制
6.Shrio
6.1 什么是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,您可以快速轻松地保护任何应用程序-从最小的移动应用程序到最大的Web和企业应用程序。
6.2 为什么要学shiro
既然shiro将安全认证相关的功能抽取出来组成一个框架,使用shiro就可以非常快速的完成认证、授权等功能的开发,降低系统成本。
shiro使用广泛,shiro可以运行在web应用,非web应用,集群分布式应用中越来越多的用户开始使用shiro。
java领域中spring security(原名Acegi)也是一个开源的权限管理框架,但是spring security依赖spring运行,而shiro就相对独立,最主要是因为shiro使用简单、灵活,所以现在越来越多的用户选择shiro。
6.3 Shiro核心架构图
7.第一个Shiro认证程序
7.1.创建项目
7.2.引入相关的依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.3.2</version>
</dependency>
7.3.shiro第一个认证程序
@Test
public void contextLoads() {
Subject subject = null;
try {
//初始化安全管理器工厂
IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini");
//通过安全管理器工厂创建一个安全管理器
SecurityManager securityManager = factory.createInstance();
//将安全管理器放入安全工具类 自动使用
SecurityUtils.setSecurityManager(securityManager);
//通过安全工具类获取主体对象
subject = SecurityUtils.getSubject();
//创建一个认证的令牌 token=身份信息+凭证信息
AuthenticationToken token = new UsernamePasswordToken("dfsf","111111");
//认证
subject.login(token);
} catch (UnknownAccountException e) {
System.out.println("未知的账号异常");
}catch (IncorrectCredentialsException e) {
System.out.println("不正确的凭证异常");
}
//判断是否认证成功
boolean authenticated = subject.isAuthenticated();
System.out.println(authenticated);
}
注意:shiro是通过抛异常的形式告诉我们是否认证通过,一般会抛出两个异常
- UnknownAccountException 未知的账号异常 用户不存在
- IncorrectCredentialsException 不正确的凭证异常 密码错误
7.4.配置shiro的配置文件
[users]
xiaohei=111111
xiaohuang=222222
8.源码追踪中的关键类
//抽象类
AuthenticatingRealm
//凭证匹配器 接口
private CredentialsMatcher credentialsMatcher;
//默认提供 SimpleCredentialsMatcher
//抽象方法
protected abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken var1) throws AuthenticationException;
// 是AuthenticatingRealm 的实现类
SimpleAccountRealm extends AuthenticatingRealm
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken upToken = (UsernamePasswordToken)token;
//根据用户名查询用户信息 upToken.getUsername()="xiaohei"
SimpleAccount account = this.getUser(upToken.getUsername());
//account xiaohei,111111
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;
}
CredentialsMatcher
//凭证匹配器
SimpleCredentialsMatcher
//做比对 equals 比对结果 true false
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
Object tokenCredentials = this.getCredentials(token); xiaohei 111111
Object accountCredentials = this.getCredentials(info); xiaohei 111111
return this.equals(tokenCredentials, accountCredentials);
}
9.shiro认证连接数据库
9.1.写自定义Realm
写自定义Realm 继承AuthenticatingRealm
public class MyRealm extends AuthenticatingRealm {
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//从令牌中获取身份信息
String username = (String) token.getPrincipal();
//查询数据库
//select * from user where username=#{username}
// User("1","xiaohei","111111")
AuthenticationInfo info=null;
if(username.equals("xiaohei")){
//将数据库查询到的数据封装到info中
info = new SimpleAuthenticationInfo("xiaohei","111111",this.getName());
}
return info;
}
}
9.2.配置连接的配置
[main]
#自定义 realm
MyRealm=com.baizhi.conf.MyRealm
#将realm设置到securityManager
securityManager.realms=$MyRealm
10.shiro加密认证
shiro默认使用的凭证匹配器是:SimpleCredentialsMatcher,
此匹配器比对凭证信息的策略是equals,所以我们需要自定义凭证匹配器
10.1.加密算法
Shiro支持的加密算法: MD2,MD5,SHA1,SHA256,SHA384,SHA512,HASH散列
@Test
public void contextLoads() {
//Md5Hash md5Hash = new Md5Hash("111111");
//Md5Hash md5Hash = new Md5Hash("111111","ABCD");
//Md5Hash md5Hash = new Md5Hash("111111","ABCD",1024);
//96e79218965eb72c92a549dd5a330112 111111
//2cdc750a3f0d0aadfac93bf6bf369699 ABCD + 111111
//e0504e77b06f5a26faf37d044c65992b ABCD + 111111 + hash散列 1024
//1 xiaohei e0504e77b06f5a26faf37d044c65992b ABCD
//Sha256Hash sha256Hash = new Sha256Hash("111111");
//bcb15f821479b4d5772bd0ca866c00ad5f926e3580720659cc80d39c9d09802a
//Sha384Hash sha384Hash = new Sha384Hash("111111");
//1b0268a40ae44c012946c974d60bf5291e7bb7c63cdb72a904d9283e3dc0a34de9afebe4035665768aaa503a4e7a30c3
//Sha512Hash sha512Hash = new Sha512Hash("111111");
//b0412597dcea813655574dc54a5b74967cf85317f0332a2591be7953a016f8de56200eb37d5ba593b1e4aa27cea5ca27100f94dccd5b04bae5cadd4454dba67d
Md5Hash md5Hash = new Md5Hash("111111","ABCD",1024);
String s = md5Hash.toHex();
System.out.println(s);
}
10.2.配置加密策略
方案一:
#定义凭证匹配器
credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher
#告诉凭证匹配器使用哪个加密算法
credentialsMatcher.hashAlgorithmName=MD5
#hash散列迭代次数
credentialsMatcher.hashIterations=1024
#将自定义的凭证匹配器告诉 自定义Realm
MyRealm.credentialsMatcher=$credentialsMatcher
方案二:
#定义凭证匹配器
credentialsMatcher=org.apache.shiro.authc.credential.Md5CredentialsMatcher
#hash散列迭代次数
credentialsMatcher.hashIterations=1024
#将自定义的凭证匹配器告诉 自定义Realm
MyRealm.credentialsMatcher=$credentialsMatcher
11.shiro授权
extends AuthorizingRealm类实现里面的认证(doGetAuthenticationInfo)和**授权(doGetAuthorizationInfo)**的方法
11.1授权
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//1.查询用户有哪些权限
String username = (String)principalCollection.getPrimaryPrincipal();
// 根据用户主身份去查询该用户有哪些角色
// admin sadmin supers
// 根据用户主身份去查询角色对应有哪些权限
//2.授予相关权限
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
if(username.equals("xiaohei")){
//授予角色
//info.addRole("admin");
//授予角色集合
info.addRoles(Arrays.asList("admin","sadmin","supers"));
//授予权限
//info.addStringPermission("user:query");
//授予权限集合
info.addStringPermissions(Arrays.asList("user:query","user:update"));
}
return info;
}
11.2 判断权限方式
a.基于角色的访问控制
//判断该主体是否有该角色
boolean hasRole = subject.hasRole("admin");
//判断该主体是否含有这些角色
boolean[] hasRoles = subject.hasRoles(Arrays.asList("admin", "supers","user"));
//判断该主体是否含有所有角色
boolean hasAllRoles = subject.hasAllRoles(Arrays.asList("admin", "supers"));
b.基于权限的访问控制
//判断该主体是否含有该权限
boolean isPermitted = subject.isPermitted("user:delete");
//判断该主体是否含有这些权限
boolean[] isPermitteds = subject.isPermitted("user:query", "user:update");
//判断该主体是否含有所有权限
boolean idPermittedAll = subject.isPermittedAll("user:query", "user:update");
11.3 权限字符串规则
权限字符串的规则是:“资源类型标识符:操作:资源实例标识符”,意思是对哪个资源的哪个实例具有什么操作,“:”是资源/操作/实例的分割符,权限字符串也可以使用*通配符。
user:query
user:update
例子:
-
用户创建权限:user:create,或user:create:*
-
用户修改实例001的权限:user:update:001
-
用户实例001的所有权限:user:*:001
12 案例
12.1 认证授权
package com.baizhi.conf;
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.authc.credential.CredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthenticatingRealm;
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 {
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//1.查询用户有哪些权限
String username = (String)principalCollection.getPrimaryPrincipal();
// 根据用户主身份去查询该用户有哪些角色
// admin sadmin supers
// 根据用户主身份去查询角色对应有哪些权限
//2.授予相关权限
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
if(username.equals("xiaohei")){
//授予角色
//info.addRole("admin");
//授予角色集合
info.addRoles(Arrays.asList("admin","sadmin","supers"));
//授予权限
//info.addStringPermission("user:query");
//授予权限集合
info.addStringPermissions(Arrays.asList("user:query","user:update"));
}
return info;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//从令牌中获取身份信息
String username = (String) token.getPrincipal();
//查询数据库
//select * from user where username=#{username}
// User("1","xiaohei","111111")
//1 xiaohei e0504e77b06f5a26faf37d044c65992b ABCD
AuthenticationInfo info=null;
if(username.equals("xiaohei")){
//将数据库查询到的数据封装到info中 密文数据
info = new SimpleAuthenticationInfo("xiaohei","e0504e77b06f5a26faf37d044c65992b", ByteSource.Util.bytes("ABCD"),this.getName());
}
return info;
}
}
12.2 测试认证授权:
package com.baizhi;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.Arrays;
@RunWith(SpringRunner.class)
@SpringBootTest
public class ShiroApplicationTests {
@Test
public void contextLoads() {
Subject subject = null;
try {
//初始化安全管理器工厂
IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini");
//通过安全管理器工厂创建一个安全管理器
SecurityManager securityManager = factory.createInstance();
//将安全管理器放入安全工具类 自动使用
SecurityUtils.setSecurityManager(securityManager);
//通过安全工具类获取主体对象
subject = SecurityUtils.getSubject();
//创建一个认证的令牌 token=身份信息+凭证信息
AuthenticationToken token = new UsernamePasswordToken("xiaohei","111111");
//认证
subject.login(token);
} catch (UnknownAccountException e) {
//e.printStackTrace();
System.out.println("未知的账号异常");
//UnknownAccountException 未知的账号异常 用户不存在
//IncorrectCredentialsException 不正确的凭证异常 密码错误
}catch (IncorrectCredentialsException e) {
//e.printStackTrace();
System.out.println("不正确的凭证异常");
//UnknownAccountException 未知的账号异常 用户不存在
//IncorrectCredentialsException 不正确的凭证异常 密码错误
}
//判断是否认证成功
boolean authenticated = subject.isAuthenticated();
//判断认证是否成功
if(authenticated){
/**基于角色的访问控制 授权*/
//判断该主体是否有该角色
//boolean b = subject.hasRole("admin");
//判断该主体是否含有这些角色
//boolean[] b = subject.hasRoles(Arrays.asList("admin", "supers","user"));
//判断该主体是否含有所有角色
boolean allRoles = subject.hasAllRoles(Arrays.asList("admin", "supers"));
/**基于权限的访问控制 授权*/
//判断该主体是否含有该权限
//boolean b = subject.isPermitted("user:delete");
//判断该主体是否含有这些权限
//boolean[] b = subject.isPermitted("user:query", "user:update");
//判断该主体是否含有所有权限
boolean permittedAll = subject.isPermittedAll("user:query", "user:update");
System.out.println("角色授权:"+allRoles);
System.out.println("权限授权:"+permittedAll);
}
}
}
13.Shiro集成SpringBoot项目
13.1.创建sprinBoot项目
父项目版本2.1.6.RELEASE
13.2.引入相关依赖
<!--shiro的jar包-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.4.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.4.2</version>
</dependency>
<!--shiro与spring整合包 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!--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>
<!-- 给内嵌tomcat提供jsp解析功能的jar-->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
</dependency>
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
13.3.配置springboot相关配置
server:
port: 8989
servlet:
context-path: /shiro
jsp:
init-parameters:
development: true
spring:
mvc:
view:
prefix: /
suffix: .jsp
13.4.配置shiroFilter配置
@Configuration
public class ShiroFilterConf {
//将shiro过滤器工厂交给spring工厂管理
@Bean
public ShiroFilterFactoryBean getUser(SecurityManager securityManager){
//创建shiro过滤器工厂
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
//将安全管理器交给shiro过滤器工厂
factoryBean.setSecurityManager(securityManager);
HashMap<String, String> map = new HashMap<>();
/*
*FormAuthenticationFilter authc 认证过滤器 只有认证成功的资源才能被访问
*AnonymousFilter anon 匿名过滤器 不用认证都可以访问
* */
map.put("/**","authc");
map.put("/test/login.jsp","anon");
map.put("/user/login","anon");
//定拦截策略 定义一个过滤器链
factoryBean.setFilterChainDefinitionMap(map);
//自定义拦截跳转的登录页面
factoryBean.setLoginUrl("/test/login.jsp");
return factoryBean;
}
//将安全管理器spring工厂管理
@Bean
public SecurityManager getSecurityManager(){
//创建安全管理器
SecurityManager securityManager = new DefaultWebSecurityManager();
return securityManager;
}
}
13.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:例子/admins/**=anon 没有参数,表示可以匿名使用。
authc:例如/admins/user/**=authc表示需要认证(登录)才能使用,没有参数
13.6.认证
自定义Realm类 继承AuthorizingRealm类 实现doGetAuthenticationInfo方法
13.6.1.认证方法
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("认证");
//获取身份信息
String username = (String)authenticationToken.getPrincipal();
AuthenticationInfo info=null;
//去数据库查询
if(username.equals("xiaohei")){
//将数据库查询到的数据封装到info中 密文数据
info = new SimpleAuthenticationInfo("xiaohei","e0504e77b06f5a26faf37d044c65992b", ByteSource.Util.bytes("ABCD"),this.getName());
}
return info;
}
13.6.2.认证配置
//将安全管理器对象交给spring工厂管理
@Bean
public SecurityManager getSecurityManager(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();
//将凭证匹配器给自定义Realm
myRealm.setCredentialsMatcher(credentialsMatcher);
return myRealm;
}
//将凭证匹配器对象交给spring工厂管理
@Bean
public HashedCredentialsMatcher getHashedCredentialsMatcher(){
//创建凭证匹配器
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
credentialsMatcher.setHashAlgorithmName("MD5"); //设置加密算法
credentialsMatcher.setHashIterations(1024); //设置散列次数
return credentialsMatcher;
}
13.6.3 登陆登出
@RequestMapping("login")
public String login(String username,String password){
try {
//认证 获得主题对象
Subject subject = SecurityUtils.getSubject();
//封装token=username+password
AuthenticationToken token=new UsernamePasswordToken(username,password);
//认证
subject.login(token);
return "redirect:/main/main.jsp";
} catch (UnknownAccountException e) {
System.out.println("未知的账号异常==账号不存在");
return "redirect:/test/login.jsp";
}catch (IncorrectCredentialsException e) {
System.out.println("不正确的凭证异常==密码错误");
return "redirect:/test/login.jsp";
}
}
@RequestMapping("logout")
public String logout(){
//认证 获得主体对象
Subject subject = SecurityUtils.getSubject();
//退出
subject.logout();
return "redirect:/test/login.jsp";
}
13.7.shiro中的标签
13.7.1.在页面引入
<%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
13.7.2.shiro标签
//没有认证展示 未登录 执行标签体的内容
<shiro:notAuthenticated></shiro:notAuthenticated>
//认证成功之后展示 已登录 执行标签体的内容
<shiro:authenticated></shiro:authenticated>
//获取身份信息
<shiro:principal/>
//判断该主体是否含有该角色
<shiro:hasRole name="user"></shiro:hasRole>
//判断该主体是否含有这些角色
<shiro:hasAnyRoles name="admin"></shiro:hasAnyRoles>
//判断该主体是否含有该权限
<shiro:hasPermission name="user:update"></shiro:hasPermission>
13.8.授权
自定义Realm类 集成 AuthorizingRealm类 实现doGetAuthorizationInfo方法
//授权
@Override
protected 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;
}
13.9.shiro配置缓存
13.9.1.导入缓存jar包
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.4.2</version>
</dependency>
13.9.2.配置缓存交给安全管理器
//将缓存对象交给spring工厂管理
@Bean
public CacheManager getCacheManager(){
//创建缓存
CacheManager cacheManager = new EhCacheManager();
return cacheManager;
}
//将安全管理器对象交给spring工厂管理
@Bean
public SecurityManager getSecurityManager(MyRealm myRealm,CacheManager cacheManager){
//创建安全管理器
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//将自定义Realm交给安全管理器
securityManager.setRealm(myRealm);
//配置缓存
securityManager.setCacheManager(cacheManager);
return securityManager;
}
14.Shiro连接数据库
14.1准备数据库表
/*
Navicat MySQL Data Transfer
Source Server : MySQL
Source Server Version : 50528
Source Host : localhost:3306
Source Database : shiro
Target Server Type : MYSQL
Target Server Version : 50528
File Encoding : 65001
Date: 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');
14.2.导入连接数据库的相关依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.4</version>
<scope>provided</scope>
</dependency>
<!--整合mybatis-->
<!--mybatis和springboot的整合包-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
<!--数据源-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.29</version>
</dependency>
<!--数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!--mybatis的核心jar-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
14.3配置数据库连接参数
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/shiro
username: root
password: root
mybatis:
type-aliases-package: com.baizhi.entity
mapper-locations: classpath:com/baizhi/mapper/*Mapper.xml
14.4 配置实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Admin {
private String adminId;
private String username;
private String password;
private String salt;
//关系属性 一个用户对应多个角色
private ArrayList<Role> roles;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Role {
private String roleId;
private String roleName;
//关系属性 一个角色对应多个权限
private ArrayList<Authority> authorities;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Authority {
private String authorityId;
private String authorityName;
}
14.5 配置DAO
public interface AdminDao {
Admin queryByUsername(String username);
Admin queryByUsernames(String username);
}
14.6.配置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">
<resultMap id="queryMap" type="Admin">
<id column="aAdminId" property="adminId"/>
<result column="username" property="username" />
<result column="password" property="password" />
<result column="salt" property="salt" />
<collection property="roles" ofType="Role" >
<id column="rRoleId" property="roleId"/>
<result column="rRoleName" property="roleName" />
<collection property="authorities" ofType="Authority">
<id column="auAuthorityId" property="authorityId"/>
<result column="auAuthorityName" property="authorityName" />
</collection>
</collection>
</resultMap>
<!--根据用户名查询用户s-->
<select id="queryByUsernames" resultMap="queryMap">
select
a.admin_id aAdminId,a.username username,a.password password,a.salt salt,
r.role_id rRoleId,r.role_name rRoleName,
au.authority_id auAuthorityId,au.authority_name auAuthorityName
from s_admin a
left join s_admin_role ar on a.admin_id=ar.admin_id
left join s_role r on ar.role_id=r.role_id
left join s_role_authority ra on r.role_id=ra.role_id
left join s_authority au on ra.authority_id=au.authority_id
where username=#{username}
</select>
<sql id="querySql">
admin_id adminId,username,password,salt
</sql>
<!--根据用户名查询用户-->
<select id="queryByUsername" resultType="Admin">
select <include refid="querySql"/> from s_admin where username=#{username}
</select>
</mapper>
14.7.认证授权连接数据库
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.awt.*;
import java.util.ArrayList;
import java.util.Arrays;
import static java.awt.SystemColor.info;
public class MyRealm extends AuthorizingRealm {
@Resource
private AdminService adminService;
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("授权");
//获取主身份信息
String username =(String)principalCollection.getPrimaryPrincipal();
//授权
//根据用户身份查角色
//根据角色查询权限
//根据用户名查角色和权限
Admin admin = adminService.queryByUsernames(username);
//角色集合
ArrayList<String> roleList = new ArrayList<>();
//权限集合
ArrayList<String> PermissionList = new ArrayList<>();
//获取所有的角色
ArrayList<Role> roles = admin.getRoles();
//遍历角色
for (Role role : roles) {
//获取角色名称
String roleName = role.getRoleName();
//将角色放入角色集合
roleList.add(roleName);
//获取权限集合
ArrayList<Authority> authorities = role.getAuthorities();
//遍历权限集合
for (Authority authority : authorities) {
//获取权限名称
String authorityName = authority.getAuthorityName();
//将权限放入权限集合
PermissionList.add(authorityName);
}
}
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//添加角色
info.addRoles(roleList);
//添加权限
info.addStringPermissions(PermissionList);
return info;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("认证");
//获取身份信息
String username = (String)authenticationToken.getPrincipal();
//根据用户名查询用户
Admin admin = adminService.queryByUsername(username);
//将数据库查询到的数据封装到info中 密文数据
AuthenticationInfo info= new SimpleAuthenticationInfo(admin.getUsername(),admin.getPassword(),ByteSource.Util.bytes(admin.getSalt()),this.getName());
/*if(username.equals("xiaohei")){
info = new SimpleAuthenticationInfo("xiaohei","e0504e77b06f5a26faf37d044c65992b", ByteSource.Util.bytes("ABCD"),this.getName());
}*/
return info;
}
}
ities();
//遍历权限集合
for (Authority authority : authorities) {
//获取权限名称
String authorityName = authority.getAuthorityName();
//将权限放入权限集合
PermissionList.add(authorityName);
}
}
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//添加角色
info.addRoles(roleList);
//添加权限
info.addStringPermissions(PermissionList);
return info;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("认证");
//获取身份信息
String username = (String)authenticationToken.getPrincipal();
//根据用户名查询用户
Admin admin = adminService.queryByUsername(username);
//将数据库查询到的数据封装到info中 密文数据
AuthenticationInfo info= new SimpleAuthenticationInfo(admin.getUsername(),admin.getPassword(),ByteSource.Util.bytes(admin.getSalt()),this.getName());
/*if(username.equals("xiaohei")){
info = new SimpleAuthenticationInfo("xiaohei","e0504e77b06f5a26faf37d044c65992b", ByteSource.Util.bytes("ABCD"),this.getName());
}*/
return info;
}
}