在此首先感谢**编程不良人**up主提供的视频教程
代码都是跟着up的视频敲的,遇到的一些问题也是通过优快云博主提供的教程解决的,在此也感谢那些提供bug解决方案的前辈们~
项目完整代码已经发布到github上面,有需要的朋友可以自取
1.权限管理
1.1 什么是权限管理
涉及到用户参与的系统都要涉及权限管理,权限管理属于系统安全范畴。权限管理实现的是对用户访问系统的控制,按照安全规则或者安全策略控制用户可以访问而且只能访问自己被授权的资源。
权限管理包括 用户身份认证 和 授权 两部分,首先对用户进行身份认证,认证通过后按照授权规则允许用户访问对应资源。
1.2 什么是身份认证
身份认证,即判断用户是否是合法用户。常见的认证方式有:登录账号+密码,指纹识别, 人脸识别,射频卡…
1.3 什么是授权
授权,即访问控制,允许不同权限级别的用户可以访问系统中的对应的系统资源
2.Shiro核心架构
Shiro是一个功能强大且易用的Java安全框架,它集成了用户身份认证,权限授权,加密,会话管理等功能。
2.1 Subject
Subject是一个概念主体,它可以是一个正在浏览网页程序的用户,也可能是一个运行中的程序。
Subject是Shiro中的一个接口,外部程序通过subject获得认证和授权,而subject则通过SecurityManager安全管理器进行认证授权。
2.2 Security Manager
Security Manager 是安全管理器,对全部的subject进行安全管理。**它是Shiro的核心,通过Security Manager 实现subject的认证和授权。实际上,Security Manager 是通过Authenticator进行认证,通过Authorizer进行授权,通过SessionManager**进行会话管理。
SecurityManager继承了Authenticator,Authorizer,SessionManager三个接口。
2.3 Authenticator
Authenticator即认证器,对用户进行身份认证,Authenticator是一个接口,shiro提供ModularRealmAuthenticator实现类,通过ModularRealmAuthenticator基本可以满足认证需求,用户还可以自行定义认证器。
2.4 Authorizer
用户在获得认证器的认证以后,需要通过授权器界定用户可以访问那些功能。
2.5 Realm
Realm即领域,相当于datasource数据源,SecurityManager对用户进行安全认证需要通过Realm获取用户数据。SecurityManager通过Realm实现对数据库数据的获取。但Realm不仅仅是获取数据,在Realm中也有认证等功能。
2.6 SessionManager
SessionManager即会话管理器,该会话管理器不依赖于web容器的session,因此shiro可以使用在非web应用上,也可以将分布式应用的会话集中在一处管理,通过该特性实现单点登录。
2.7 SessionDao
SessionDao即会话Dao,是对session会话操作的一套接口,可以通过该接口实现会话的数据库存储。
2.8 CacheManager
缓存管理,将用户的权限数据存储在缓存,这样减少数据库IO,提高系统性能。
2.9 CryptoGraphy
密码管理,是一套加密解密组件,方便开发。提供常见的散列,加/解密功能。
3.Shiro中的认证
3.1认证的关键对象与流程
身份认证就是判断一个用户是否为合法用户的过程。常用的认证方式有账户和口令,通过匹配系统中的账户和口令来判断用户是否合法。
-
**subject:**主体,访问系统的主体(用户,程序)
-
**Principal:**身份信息,是主体身份标识,标识必须具有唯一性。例如:手机号码,邮箱,用户名等;一个用户可以有多个身份信息,但是必须要有一个主身份信息-Primary Principal。
-
Crendential:凭证信息,只有主体知道的安全信息,比如密码,证书等等。
3.2 认证开发
3.2.1简单认证demo
创建maven项目,导入shiro坐标:
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.5.3</version>
</dependency>
设置shiro.ini,配置用户信息
[users]
zhangsan=123
wangwu=456
lisi=789
创建TestAuthenticator类
package im.hwp;
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.首先获取SecurityManager对象
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
//2.设置defaultSecurityManager对象的Realm
defaultSecurityManager.setRealm(new IniRealm("classpath:shiro.ini"));
//3.给SecurityUtils全局工具类绑定defaultSecurityManager对象
SecurityUtils.setSecurityManager(defaultSecurityManager);
//4.通过SecurityUtils类获取Subject主体
Subject subject = SecurityUtils.getSubject();
//5. 设置登录信息,创建登录令牌
UsernamePasswordToken token = new UsernamePasswordToken("zhangsan","1231");
//6. 使用该令牌进行登录
try{
System.out.println(subject.isAuthenticated());
subject.login(token);
System.out.println(subject.isAuthenticated());
}catch(UnknownAccountException exception){
//在认证过程中,如果用户名不存在,会抛出UnknownAccountException
exception.printStackTrace();
System.out.println("认证失败:用户名不存在");
}catch(IncorrectCredentialsException exception){
//认证过程中,如果用户名正常而密码错误,会抛出IncorrectCredentialsException
exception.printStackTrace();
System.out.println("认证失败:密码错误");
}
}
}
3.2.2 认证流程的源码分析
阅读源码后发现,真正实现用户名(身份信息)检查的类是SimpleAccountRealm.class,它继承自AuthorizingRealm类
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//将token抢转为UsernamePasswordToken
UsernamePasswordToken upToken = (UsernamePasswordToken)token;
//根据用户名获取到account的信息,其中包括登录密码
SimpleAccount account = this.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;
}
在检查用户名合法以后,会继续核验用户的密码是否正确,该方法assertCredentialsMatch在AuthenticatingRealm类中实现。
protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
CredentialsMatcher cm = this.getCredentialsMatcher();
if (cm != null) {
if (!cm.doCredentialsMatch(token, info)) {
String msg = "Submitted credentials for token [" + token + "] did not match the expected credentials.";
throw new IncorrectCredentialsException(msg);
}
} else {
throw new AuthenticationException("A CredentialsMatcher must be configured in order to verify credentials during authentication. If you do not wish for credentials to be examined, you can configure an " + AllowAllCredentialsMatcher.class.getName() + " instance.");
}
}
分别获取用户提供的密码和系统查询到的密码,并实现比较的代码:
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
Object tokenCredentials = this.getCredentials(token);
Object accountCredentials = this.getCredentials(info);
return this.equals(tokenCredentials, accountCredentials);
}
AuthenticatingRealm ——> doGetAuthenticationInfo, 该方法用于认证管理
AuthorizingRealm ——> doGetAuthorizationInfo,该方法用于授权管理
在后期自己开发Realm时,需要定义一个类,该类继承AuthorizingRealm,并重写其中doGetAuthenticationInfo和doGetAuthorizationInfo方法。
3.2.3 使用MD5 + Salt 算法实现认证功能
realm中方法通过传入的token获取到principal(用户名),使用用户名去数据库获取用户的密码。将用户的用户名和密码封装到SimpleAuthenticationInfo对象,由shiro执行密码检查。
要点解析:
a.要实现MD5 + Salt,需要在用户初次注册时,对密码进行MD5 + Salt运算,此时存入数据库的数据是已经加密过的。
b.在实现Realm时,需要在Realm中认证方法返回值SimpleAuthenticationInfo定义用户身份信息,密码,随机盐以及realm对象
//认证
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String principal = (String) authenticationToken.getPrincipal();
return new SimpleAuthenticationInfo(
principal,
"752ed83bdebff1a1566c20ca2ff4b164",
ByteSource.Util.bytes("hwp"),
this.getName()
);
}
c.为realm对象绑定密码验证器,需要指定密码验证器的加密算法和hash次数
public static void main(String[] args) {
// //1.创建realm对象
CustomerRealm customerRealm = new CustomerRealm();
//2. 设置realm的比较器
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5");
hashedCredentialsMatcher.setHashIterations(1024);
customerRealm.setCredentialsMatcher(hashedCredentialsMatcher);
//3. 创建SecurityManager对象
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
//4.设置defaultSecurityManager对象的Realm
defaultSecurityManager.setRealm(customerRealm);
//5.给SecurityUtils全局工具类绑定defaultSecurityManager对象
SecurityUtils.setSecurityManager(defaultSecurityManager);
//4.通过SecurityUtils类获取Subject主体
Subject subject = SecurityUtils.getSubject();
//5. 设置登录信息,创建登录令牌
UsernamePasswordToken token = new UsernamePasswordToken("zhangsan","123");
//6. 使用该令牌进行登录
try{
System.out.println(subject.isAuthenticated());
subject.login(token);
System.out.println(subject.isAuthenticated());
}catch(UnknownAccountException exception){
exception.printStackTrace();
System.out.println("认证失败:用户名不存在");
}catch(IncorrectCredentialsException exception){
exception.printStackTrace();
System.out.println("认证失败:密码错误");
}
}
4.Shiro的授权
4.1 概念
授权,即认证之后的用户进行权限管理。主要是对用户进行系统资源访问权限的授权。
4.2 关键对象
授权可以简单理解为who对what进行How操作
Who:主体(Subject)
What:系统资源(Resource),如系统方法,页面,按钮,系统商品信息等。资源包括资源类型
和资源实例
How:权限/许可(Permission),规定了主体对资源的操作许可。
4.3 授权方式
-
基于角色
基于角色的访问控制,是以角色为中心进行访问控制
if(subject.hasRole("admin")){ //操作资源 }
-
基于资源
基于资源的访问控制,是以资源为中心进行访问控制
if(subject.isPermission("user:create:*")){ //所有的用户具有创建权限 }
4.4 权限字符串
权限字符串的规则是:资源标识符
:操作
:资源实例标识符
,意思是对哪个资源的哪个实例具有什么操作,权限字符串也可以使用*作为通配符。
例子:
- 用户创建权限:user:create, 或者 user:create:*
- 用户修改实例001的权限: user:update:001
- 用户实例001的所有权限: user:*:001
4.6 Shiro中授权编程的方式
-
编程式
Subject subject = SecurityUtils.getSubject(); if(subject.hasRole("admin")){ //有权限 }else{ //没有权限 }
-
注解式
@RequiresRoles("admin") public void hello(){ //有权限 }
-
标签式
<shiro:hasRole name="admin"> <!- 有权限 -> </shiro:hasRole>
4.7 Shiro开发认证
权限的获取部分:
//在自定义的Realm类中设置授权
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获取当前的用户名
Object primaryPrincipal = principalCollection.getPrimaryPrincipal();
//获取用户名以后,应该去缓存或者数据库查询出该用户对应的权限信息
// 创建simpleAuthorizationInfo对象,并将查询到的角色信息添加到该对象中,然后返回该对象。
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.addRole("admin");
simpleAuthorizationInfo.addRole("user");
//基于权限字符串的权限控制
simpleAuthorizationInfo.addStringPermission("user:*:01");
simpleAuthorizationInfo.addStringPermission("product:create");
return simpleAuthorizationInfo;
}
权限检查部分:
if(subject.isAuthenticated()){
//单一角色情况
System.out.println(subject.hasRole("admin"));
//多个角色情况,该方法返回一个Boolean list,对应不同角色的True或者False
System.out.println(subject.hasRoles(Arrays.asList("admin", "user")));
//多个角色同时满足情况,全部满足返回True,任意一个不满足则返回False
System.out.println(subject.hasAllRoles(Arrays.asList("admin", "user")));
//基于权限字符串
System.out.println(subject.isPermitted("user:create"));//false
System.out.println(subject.isPermitted("product:create:001"));//true
}
5. SpringBoot整合Shiro实战
5.1 快速开始
-
创建SpringBoot工程,导入相关依赖
<!-- 添加servlet依赖模块 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <scope>provided</scope> </dependency> <!-- 添加jstl标签库依赖模块 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> </dependency> <!-- springboot内置tomcat没有此依赖 --> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> <scope>provided</scope> </dependency> <!--引入shir整合springboot依赖--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-starter</artifactId> </dependency>
-
配置application.properties
server.port=8888 server.servlet.context-path=/shiro spring.application.name=shiro spring.mvc.view.prefix=/ spring.mvc.view.suffix=.jsp
-
在main目录下新建webapp目录,创建index.jsp文件以及login.jsp文件
<!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> <p>hello world</p> </body> </html>
<!doctype html> <%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> <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> 登录 <form action="${pageContext.request.contextPath}/user/login" method="post"> 用户:<input name="username" type="text"> 密码:<input name="password" type="password"> <input type="submit" value="登录"> </form> </body> </html>
-
在项目configuration–>Environment下设置working directory为
$MODULE_WORKING_DIR$
-
启动Spring Boot 工程,在浏览器键入地址:localhost:8888/shiro/index.jsp访问页面
5.2 配置ShiroConfig类
在im.hwp路径下创建config文件夹,创建ShiroConfig类
package im.hwp.config;
import im.hwp.shiro.CustomerRealm;
import org.apache.shiro.realm.Realm;
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 java.util.HashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean getShiroFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
//1.创建ShiroFilterFactoryBean
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//2.为shiroFactoryBean对象绑定SecurityManager,在web项目中,需要绑定defaultWebSecurityManager对象
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
//3. 配置系统公共资源和系统受限资源
Map<String,String> map = new HashMap<String,String>();
map.put("/login","anon");//表示公共资源,无需认证即可访问
//map.put("/index.jsp","authc");
map.put("/**","authc");//这种表达方式表明所有的资源都需要认证
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
//4.设置web的登录页面,当需要认证和授权时,自动跳转该页面
shiroFilterFactoryBean.setLoginUrl(