目录
【2.2.2】编写SecurityService(测试功能使用的是假数据)
第一章 权限概述
1、什么是权限
权限管理,一般指根据系统设置的安全策略或者安全规则,用户可以访问而且只能访问自己被授权的资源,不多不少。权限管理几乎出现在任何系统里面,只要有用户和密码的系统。
权限管理在系统中一般分为:
-
访问权限
一般表示你能做什么样的操作,或者能够访问那些资源。例如:给张三赋予“人事主管”角色,“人事主管”具有“查询员工”、“添加员工”、“修改员工”和“删除员工”权限。此时张三能够进入系统,则可以进行这些操作
-
数据权限
一般表示某些数据你是否属于你,或者属于你可以操作范围。例如:李四是"项目经理"角色,他可以看他的员工所有的任务分解,他的员工只能看自己负责的任务分解
2、身份认证概念-Authentication
【1】什么是认证
身份认证,就是判断一个用户是否为合法用户的处理过程。最常用的简单身份认证方式是系统通过核对用户输入的用户名和密码,看其是否与系统中存储的该用户的用户名和密码一致,来判断用户身份是否正确。例如:密码登录,手机短信验证、三方授权等
【2】对象
Subject:主体:访问系统的用户,主体可以是用户、程序等,进行认证的都称为主体,简单理解就是当前用户;
Principal:身份信息是主体(subject)进行身份认证的标识,标识必须具有唯一性,如用户名、手机号、邮箱地址等,一个主体可以有多个身份,但是必须有一个主身份(Primary Principal),简单理解就是当前账号。
credential:凭证信息:是只有主体自己知道的安全信息,如密码、证书等,简单理解就是密码。
2、用户授权概念-Authorization
【1】什么是授权
授权,即访问控制,控制谁能访问哪些资源。主体进行身份认证后,系统会为其分配对应的权限,当访问资
源时,会校验其是否有访问此资源的权限。
这里首先理解4个对象。
用户对象subject:当前操作的用户、程序,例如:zhangSan,liSi,admin1。
资源对象resource:当前被访问的对象,如系统菜单、页面、按钮、方法、系统商品信息等
访问类型:商品菜单,订单菜单、分销商菜单
数据类型:我的商品,我的订单,我的评价
角色对象role :一组 "权限操作许可权" 的集合。
权限对象permission:权限操作许可权
我的商品(资源)===>访问我的商品(权限许可)
分销商菜单(资源)===》访问分销商列表(权限许可)
【2】授权流程
第二章 Shiro概述
1、Shiro简介
【1】什么是Shiro?
Shiro是apache旗下一个开源框架,它将软件系统的安全认证相关的功能抽取出来,实现用户身份认证,权限授权、加密、会话管理等功能,组成了一个通用的安全认证框架。
【2】Shiro 的特点
Shiro 是一个强大而灵活的开源安全框架,能够非常清晰的处理认证、授权、管理会话以及密码加密。如下是它所具有的特点:
· 易于理解的 Java Security API;
· 简单的身份认证(登录),支持多种数据源(LDAP,JDBC 等);
· 对角色的简单的签权(访问控制),也支持细粒度的鉴权;
· 支持一级缓存,以提升应用程序的性能;
· 内置的基于 POJO 企业会话管理,适用于 Web 以及非 Web 的环境;
· 异构客户端会话访问;
· 非常简单的加密 API;
· 不跟任何的框架或者容器捆绑,可以独立运行。
2、核心组件
-
Shiro架构图
-
Subject
Subject主体,外部应用与subject进行交互,subject将用户作为当前操作的主体,这个主体:可以是一个通过浏览器请求的用户,也可能是一个运行的程序。Subject在shiro中是一个接口,接口中定义了很多认证授相关的方法,外部程序通过subject进行认证授,而subject是通过SecurityManager安全管理器进行认证授权
-
SecurityManager
SecurityManager权限管理器,它是shiro的核心,负责对所有的subject进行安全管理。通过SecurityManager可以完成subject的认证、授权等,SecurityManager是通过Authenticator进行认证,通过Authorizer进行授权,通过SessionManager进行会话管理等。SecurityManager是一个接口,继承了Authenticator, Authorizer, SessionManager这三个接口
-
Authenticator
Authenticator即认证器,对用户登录时进行身份认证
-
Authorizer
Authorizer授权器,用户通过认证器认证通过,在访问功能时需要通过授权器判断用户是否有此功能的操作权限。
-
Realm(数据库读取+认证功能+授权功能实现)
Realm领域,相当于datasource数据源,securityManager进行安全认证需要通过Realm获取用户权限数据 比如: 如果用户身份数据在数据库那么realm就需要从数据库获取用户身份信息。 注意: 不要把realm理解成只是从数据源取数据,在realm中还有认证授权校验的相关的代码。
-
SessionManager
SessionManager会话管理,shiro框架定义了一套会话管理,它不依赖web容器的session,所以shiro可以使用在非web应用上,也可以将分布式应用的会话集中在一点管理,此特性可使它实现单点登录。
-
SessionDAO
SessionDAO即会话dao,是对session会话操作的一套接口 比如: 可以通过jdbc将会话存储到数据库 也可以把session存储到缓存服务器
-
CacheManager
CacheManager缓存管理,将用户权限数据存储在缓存,这样可以提高性能
-
Cryptography
Cryptography密码管理,shiro提供了一套加密/解密的组件,方便开发。比如提供常用的散列、加/解密等功能
第三章 Shiro入门
1、身份认证
【1】基本流程
流程如下:
1、Shiro把用户的数据封装成标识token,token一般封装着用户名,密码等信息
2、使用Subject门面获取到封装着用户的数据的标识token
3、Subject把标识token交给SecurityManager,在SecurityManager安全中心中,SecurityManager把标识token委托给认证器Authenticator进行身份验证。认证器的作用一般是用来指定如何验证,它规定本次认证用到哪些Realm
4、认证器Authenticator将传入的标识token,与数据源Realm对比,验证token是否合法
【2】案例演示
【2.1】需求
1、使用shiro完成一个用户的登录
【2.2】实现
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
【2.2.1】新建项目
shiro
【2.2.3】编写shiro.ini
[users]
zhanSan=123
liSi=456
【2.2.4】测试类验证测试
@Test
void contextLoads() {
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
Subject subject = SecurityUtils.getSubject();
log.info("是否登录成功"+String.valueOf(subject.isAuthenticated()));
subject.login(new UsernamePasswordToken("zhangSan","admin"));
log.info("是否登录成功"+String.valueOf(subject.isAuthenticated()));
}
【2.2.4】测试
当账号密码正确的时候代码可以继续执行
当错误的时候,会抛出异常
UnknownAccountException 账号不存在异常
IncorrectCredentialsException 认证失败异常
tips:
Incorrect 不正确
Credentials 认证
【2.3】小结
1、权限定义:ini文件
2、加载过程:
导入权限ini文件构建权限工厂
工厂构建安全管理器
使用SecurityUtils工具生效安全管理器
使用SecurityUtils工具获得主体
使构建账号token用SecurityUtils工具获得主体
构建账号token
登录操作----------抛出异常
2、Realm(重要)
【1】Realm接口
所以,一般在真实的项目中,我们不会直接实现Realm接口,我们一般的情况就是直接继承AuthorizingRealm,能够继承到认证与授权功能。它需要强制重写两个方法
【2】自定义Realm
【2.1】需求
1、自定义Realm,取得密码用于比较
【2.2】实现
【2.2.1】创建项目
shiro01
【2.2.2】定义SecurityService
SecurityService
package com.itqq.service;
public interface SecurityService {
String findPasswordByLoginName(String loginName);
}
SecurityServiceImpl
package com.itqq.shiro.service.impl;
import com.itheima.shiro.service.SecurityService;
/**
* @Description:权限服务层
*/
public class SecurityServiceImpl implements SecurityService {
@Override
public String findPasswordByLoginName(String loginName) {
return "123";
}
}
【2.2.3】定义MyRealm
package com.itqq.realm;
import com.haogu.service.SecurityService;
import com.haogu.service.impl.SecurityServiceImpl;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
public class MyRealm extends AuthorizingRealm {
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String) token.getPrincipal();
SecurityService securityService = new SecurityServiceImpl();
String password = securityService.findPasswordByUsername(username);
if("".equals(password) || password == null){
throw new UnknownAccountException("账号不存在");
}
return new SimpleAuthenticationInfo(username,password,getName());
}
}
【2.2.4】编辑shiro.ini
#声明自定义的realm,且为安全管理器指定realms
[main]
myRealm=com.itqq.realm.MyRealm
securityManager.realms=$myRealm
##[users]
##zhangSan=123
3、散列算法-SimpleHash
散列算法一般用于生成数据的摘要信息,是一种不可逆的算法,一般适合存储密码之类的数据,常见的散列算法如MD5、SHA等。一般进行散列时最好提供一个salt(盐),比如加密密码“hello123”,产生的散列值是“be3ee20eac72da4ef6b233814bb61e67afab7af1”,可以到一些md5解密网站很容易的通过散列值得到密码“admin”,即如果直接对密码进行散列相对来说破解更容易,此时我们可以加一些只有系统知道的干扰数据,如salt(即盐);这样散列的对象是“密码+salt”,这样生成的散列值相对来说更难破解。
shiro支持的散列算法:
Md2Hash、Md5Hash、Sha1Hash、Sha256Hash、Sha384Hash、Sha512Hash
@Test
public void m1() {
// algorithmName 加密方式:md5,md2,sha-1等
// source 加密内容
// salt 盐
// hashIterations 加密次数
String password = new SimpleHash("md5","hello123","dvfdhj",512).toHex();
System.out.println(password);
}
4、Realm使用散列算法
上面我们了解散列算法,那么在realm中怎么使用?在realm02中我们使用的密码是明文的校验方式,也就是SecurityServiceImpl中findPasswordByUsername返回的是明文123的密码
【1】新建项目
shiro01/MD503
【2】创建密文密码
password:c8bb39206310191b78d22680243afc42 salt:dvfdhj 原密码:hello123
【3】修改SecurityService
SecurityService修改成返回salt和password的map
package com.itqq.service.impl;
import com.itqq.service.SecurityService;
public class SecurityServiceImpl implements SecurityService {
@Override
public String findPasswordByUsername(String username) {
return "c8bb39206310191b78d22680243afc42";
}
}
【4】指定密码匹配方式
为MyRealm类添加构造方法如下:
public MyRealm(){
//指定密码匹配方式为md5
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher("md5");
//指定密码迭代次数
matcher.setHashIterations(512);
//使用父亲方法使匹配方式生效
setCredentialsMatcher(matcher);
}
修改MyRealm类的认证doGetAuthenticationInfo方法如下
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String) token.getPrincipal();
SecurityService securityService = new SecurityServiceImpl();
String password = securityService.findPasswordByUsername(username);
if("".equals(password) || password == null){
throw new UnknownAccountException("账号不存在");
}
// 第1个参数:账号
// 第2个参数:密码
// 第3个参数:盐
// 第4个参数:real的名字
return new SimpleAuthenticationInfo(username,password, ByteSource.Util.bytes("dvfdhj"),getName());
}
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken token1 = (UsernamePasswordToken) token;
String username = token1.getUsername();
if(!StringUtils.hasText(username)){
throw new AuthenticationException("用户名不能为空");
}
String password = new String(token1.getPassword());
if(!StringUtils.hasText(password)){
throw new AuthenticationException("密码不能为空");
}
User user = securityService.findByUsername(username);
if (user == null){
return null;
}
// 密码判断:shiro框架自行判断--与用户输入的账号和密码进行对比,验证token是否合法
// user.getId(),是shiro框架提供的可以改变的值,一般是id
// 原本写是username/phone --------> id
return new SimpleAuthenticationInfo(user.getId(),user.getPassword(), ByteSource.Util.bytes("adagag"),getName());
}
5、用户授权
【1】基本流程
1、首先调用Subject.isPermitted/hasRole接口,其会委托给SecurityManager。
2、SecurityManager接着会委托给内部组件Authorizer;
3、Authorizer再将其请求委托给我们的Realm去做;Realm才是真正干活的;
4、Realm将用户请求的参数封装成权限对象。再从我们重写的doGetAuthorizationInfo方法中获取从数据库中查询到的权限集合。
5、Realm将用户传入的权限对象,与从数据库中查出来的权限对象,进行一一对比。如果用户传入的权限对象在从数据库中查出来的权限对象中,则返回true,否则返回false。
进行授权操作的前提:用户必须通过认证。
在真实的项目中,角色与权限都存放在数据库中。
好了,接下来,我们要重写我们本小节的核心方法了。在MyRealm中找到下列方法:
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
此方法的传入的参数PrincipalCollection principals,是一个包装对象,它表示"用户认证凭证信息"。包装的是谁呢?没错,就是认证doGetAuthenticationInfo()方法的返回值的第一个参数username。你可以通过这个包装对象的getPrimaryPrincipal()方法拿到此值,然后再从数据库中拿到对应的角色和资源,构建SimpleAuthorizationInfo。
/**
* @Description 授权方法
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//拿到用户认证凭证信息
String loginName = (String) principals.getPrimaryPrincipal();
//从数据库中查询对应的角色和资源
SecurityService securityService = new SecurityServiceImpl();
List<String> roles = securityService.findRoleByloginName(loginName);
List<String> permissions = securityService.findPermissionByloginName(loginName);
//构建资源校验
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.addRoles(roles);
authorizationInfo.addStringPermissions(permissions);
return authorizationInfo;
}
【2】案例演示
【2.1】需求
1、实现doGetAuthorizationInfo方法实现鉴权 2、使用subject类实现权限的校验
【2.2】实现
【2.2.1】创建项目
shiro01/authentication-realm04
【2.2.2】编写SecurityService(测试功能使用的是假数据)
在SecurityService中添加
package com.itqq.service;
import java.util.List;
public interface UserService {
String findPasswordByUsername(String username);
List<String> findRolesByUsername(String username);
List<String> findPermissionByUsername(String username);
}
SecurityServiceImpl添加实现
package com.itqq.service.impl;
import com.itqq.service.UserService;
import java.util.ArrayList;
import java.util.List;
public class UserServiceImpl implements UserService {
@Override
public String findPasswordByUsername(String username) {
if ("admin".equals(username)){
return "4d5233a24fd625a06437778c4ed76969"; // 123
}else if("zhangSan".equals(username)){
return "a7b19b47e46ce6ca9860c326db041859"; // 1234
}else if("liSi".equals(username)){
return "a7b19b47e46ce6ca9860c326db041859"; // 1234
}
return null;
}
@Override
public List<String> findRolesByUsername(String username) {
List<String> roles = new ArrayList<>();
roles.add("普通用户");
if("admin".equals(username)){
roles.add("管理员");
roles.add("VIP用户");
}else if("liSi".equals(username)){
roles.add("VIP用户");
}
return roles;
}
@Override
public List<String> findPermissionByUsername(String username) {
List<String> permissions = new ArrayList<>();
permissions.add("comment:select"); // 评论表的查询权限
permissions.add("comment:insert"); // 评论表的添加权限
if("admin".equals(username)){
// permissions.add("user:select");
// permissions.add("user:insert");
// permissions.add("user:update");
// permissions.add("user:delete");
permissions.add("*:*"); // 所有表的所有权限
}
if("liSi".equals(username) ){
// permissions.add("comment:update");
// permissions.add("comment:delete");
permissions.add("comment:*"); // 评论表的所有权限
}
return permissions;
}
}
【2.2.3】编写MyRealm
在MyRealm中修改doGetAuthorizationInfo方法如下
package com.itqq.realm;
import com.itqq.service.SecurityService;
import com.itqq.service.impl.SecurityServiceImpl;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
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.List;
public class MyRealm extends AuthorizingRealm {
public MyRealm(){
//指定密码匹配方式为md5
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher("md5");
//指定密码迭代次数
matcher.setHashIterations(512);
//使用父亲方法使匹配方式生效
setCredentialsMatcher(matcher);
}
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//拿到账号
String username = (String) principals.getPrimaryPrincipal();
// SecurityServiceImpl()不在ioc容器中所以要new一个对象,不能注入
SecurityService securityService = new SecurityServiceImpl();
// 获取角色
List<String> roles = securityService.findRoleByUsername(username);
// 获取权限
List<String> permissions = securityService.findPermissionByUsername(username);
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.addRoles(roles);
authorizationInfo.addStringPermissions(permissions);
return authorizationInfo;
}
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String) token.getPrincipal();
SecurityService securityService = new SecurityServiceImpl();
String password = securityService.findPasswordByUsername(username);
if("".equals(password) || password == null){
throw new UnknownAccountException("账号不存在");
}
return new SimpleAuthenticationInfo(username,password, ByteSource.Util.bytes("dvfdhj"),getName());
}
}
【2.2.4】编写App
package com.itqq;
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.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
public class App
{
public static void main( String[] args ) {
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
Subject subject = SecurityUtils.getSubject();
try {
subject.login(new UsernamePasswordToken("zhangSan","1234"));
}catch (IncorrectCredentialsException e){
System.out.println("密码错误");
}catch (UnknownAccountException e){
System.out.println("账号不存在");
}
System.out.println(subject.isAuthenticated());
System.out.println("管理员:"+ subject.hasRole("管理员"));
System.out.println("普通用户:"+ subject.hasRole("普通用户"));
System.out.println("VIP用户:"+ subject.hasRole("VIP用户"));
// has是返回boolean值,check抛出异常
// subject.checkRole("管理员"); // UnauthenticatedException
System.out.println("用户表添加:"+subject.isPermitted("user:insert"));
System.out.println("评论表的查询:"+subject.isPermitted("comment:select"));
// UnauthorizedException: Subject does not have permission [comment:update]
subject.checkPermission("comment:update");
}
}
【3】小结
1、鉴权需要实现doGetAuthorizationInfo方法
2、鉴权使用门面subject中方法进行鉴权
以check开头的会抛出异常
以is和has开头会返回布尔值
总结:
Shiro框架的几个核心组件:
-
Subject - 这是Shiro的安全主体,代表了应用程序用户。它封装了所有与安全操作相关的方法,如登录、登出、权限检查等。
-
Realms - Realm是Shiro与应用数据源(如数据库)交互的模块,用于获取用户信息和角色权限。你可以定义多个Realm来从不同的数据源获取信息。
-
SecurityManager - 它是Shiro的核心,负责管理Subject、Realms和其他安全相关的组件。它是Shiro的“大脑”,协调所有的安全操作。
-
Session Manager - 用于会话管理和状态跟踪,可以控制用户的会话生命周期,并提供跨域会话管理能力。
-
Caching - Shiro提供了缓存机制,可以提高安全性操作的性能,如缓存认证和授权结果。
-
Cryptography - 提供加密和散列功能,用于保护敏感数据。
Shiro的设计使得它可以轻松集成到任何Java应用中,无论是Web应用还是独立的Java应用。它的灵活性和可扩展性使得它成为企业级应用安全解决方案的首选。
总结结束!!!