菜鸟的shiro学习总结
说明
更新时间:2020/9/8 10:15,更新到了springboot整合
更新时间:2020/9/3 16:14,更新到了入门的第三个案例
更新时间:2022/6/12 18:27,更新了加密算法和入门完整demo
本文主要对shiro的学习总结,shiro的功能跟security的功能是类似的,据网上的说法,shiro相对于security较轻量级一点,但security功能较多。本文会持续更新,不断地扩充
本文仅为记录学习轨迹,如有侵权,联系删除
一、核心知识
(1)简介
Apache Shiro 是 Java 的一个安全框架。目前,使用 Apache Shiro 的人越来越多,因为它相当简单,对比 Spring Security,可能没有 Spring Security 做的功能强大,但是在实际工作时可能并不需要那么复杂的东西,所以使用小而简单的 Shiro 就足够了。对于它俩到底哪个好,这个不必纠结,能更简单的解决项目问题就好了。
(2)核心架构
这是shiro的官网给出的一张核心架构图,包括认证、授权和加密等
模块 | 说明 |
---|---|
Authentication | 身份认证 / 登录,验证用户是不是拥有相应的身份; |
Authorization | 授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限; |
Session Management | 会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通 JavaSE 环境的,也可以是如 Web 环境的; |
Session Dao | 对会话数据进行curd操作,操作Session Management中数据 |
Cryptography | 加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储; |
Caching Manager | 缓存,比如用户登录后,其用户信息、拥有的角色 / 权限不必每次去查,这样可以提高效率; |
Pluggable Realms | 可以自定义realm,获取用户认证和授权的相应数据,以完成认证和授权 |
二、加密
先来了解一下加密算法,一个正规一点的系统,一般都有自己的用户体系用来支持登录和权限的认证,而且用户的密码一般都是经过加密后存储在数据库的,一般不会存储明文密码,如果存储明文,一旦数据库被挟持,整个系统的数据安全就会受到威胁。
先简单介绍一下一些常见的加密方法,shiro集成了一些常见的加密算法,使用前先引入shiro依赖
这里使用maven项目,需要引入shiro的依赖
<!--shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.6.0</version>
</dependency>
(1)对称加密
对称加密(也叫私钥加密),指加密和解密使用相同密钥的加密算法。有时又叫传统密码算法,就是加密密钥能够从解密密钥中推算出来,同时解密密钥也可以从加密密钥中推算出来。而在大多数的对称算法中,加密密钥和解密密钥是相同的,所以也称这种加密算法为秘密密钥算法或单密钥算法。它要求发送方和接收方在安全通信之前,商定一个密钥。对称算法的安全性依赖于密钥,泄漏密钥就意味着任何人都可以对他们发送或接收的消息解密,所以密钥的保密性对通信的安全性至关重要。
常见的加密算法有AES、DES等,shiro集成了有AES,下面给个demo
/**
* AES 对称加密演示
*/
public static void main(String[] args) {
String password = "123";
System.out.println("原密码:" + password);
AesCipherService aesCipherService = new AesCipherService();
//设置key长度
aesCipherService.setKeySize(128);
//生成key
Key key = aesCipherService.generateNewKey();
System.out.println("密钥:" + key);
// 用key进行加密
String passwordOfEncrypt = aesCipherService.encrypt(password.getBytes(), key.getEncoded()).toHex();
System.out.println("加密后的密码:" + passwordOfEncrypt);
// 用key进行解密
String passwordOfDecrypt = new String(aesCipherService.decrypt(Hex.decode(passwordOfEncrypt), key.getEncoded()).getBytes());
System.out.println("解密后的密码:" + passwordOfDecrypt);
}
也就是说只要有这个密钥,就可以对密码进行解密
(2)非对称加密
对于非对称加密,最常用的就是RSA和DSA,非对称加密有公钥和私钥两个概念,私钥自己拥有,不能给别人,公钥公开。根据应用的不同,我们可以选择使用不同的密钥加密:
- 签名:使用私钥加密,公钥解密。用于让所有公钥所有者验证私钥所有者的身份并且用来防止私钥所有者发布的内容被篡改,但是不用来保证内容不被他人获得。
- 加密:用公钥加密,私钥解密。用于向公钥所有者发布信息,这个信息可能被他人篡改,但是无法被他人获得。
这个了解一下就行
(3)摘要加密
摘要算法是一种能产生特殊输出格式的算法,这种算法的特点是:无论用户输入什么长度的原始数据,经过计算后输出的密文都是固定长度的,这种算法的原理是根据一定的运算规则对原数据进行某种形式的提取,这种提取就是摘要,被摘要的数据内容与原数据有密切联系,只要原数据稍有改变,输出的“摘要”便完全不同,因此,基于这种原理的算法便能对数据完整性提供较为健全的保障。
但是,由于输出的密文是提取原数据经过处理的定长值,所以它已经不能还原为原数据,即消息摘要算法是不可逆的,理论上无法通过反向运算取得原数据内容,因此它通常只能被用来做数据完整性验证。
最常见的有MD5加密,下面用shiro演示一下
public static void main(String[] args) {
//默认的MD5加密,默认hash散列一次
Md5Hash md5Hash1 = new Md5Hash("123");
String s1 = md5Hash1.toHex();
System.out.println("默认的MD5加密 => "+s1);
//MD5 + salt
//默认salt加在123前面
Md5Hash md5Hash2 = new Md5Hash("123", "q1v*(%");
String s2 = md5Hash2.toHex();
System.out.println("MD5+salt => "+s2);
//MD5 + salt + hash散列
Md5Hash md5Hash3 = new Md5Hash("123","q1v*(%",1024);
String s3 = md5Hash3.toHex();
System.out.println("MD5 + salt + hash => "+s3);
}
MD5的加密是不可逆的,就是说无法解密,那用户登录的时候,如果判断用户是否密码正确呢?可以用下面的方式
假如用户密码是:123,注册的时候把密码加密成202cb962ac59075b964b07152d234b70,把加密后的密码存储到数据库
下次用户要登录的时候,输入123,这个时候把123用同样的MD5加密,然后与数据库加密的那个密码比对即可知道密码是否正确了
三、入门系列
shiro支持单机应用和web应用,并且提供有相应的坐标依赖
(1)第一个入门案例(单机应用)
新建一个maven项目,总体结构如图
并且引入相应的坐标依赖
<!--shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.6.0</version>
</dependency>
配置用户名和密码,这里用shiro.ini配置文件进行配置,实际开发要使用查看数据库
[users]
zs=123
ls=456
ww=789
实现的代码
public class Shiro01 {
public static void main(String[] args) {
//1.创建安全管理器对象
DefaultSecurityManager securityManager = new DefaultSecurityManager();
//2.给安全管理器设置realm
securityManager.setRealm(new IniRealm("classpath:shiro.ini"));
//3.securityManager给全局安全工具类设置安全管理器
SecurityUtils.setSecurityManager(securityManager);
//4.关键对象 subject 主体
Subject subject = SecurityUtils.getSubject();
//5.创建令牌
UsernamePasswordToken token = new UsernamePasswordToken();
token.setUsername("z1");//用户名
token.setPassword("123".toCharArray());//密码
//6.用户认证
try {
System.out.println("认证前状态:"+subject.isAuthenticated());
subject.login(token);
System.out.println("认证后状态:"+subject.isAuthenticated());
} catch (UnknownAccountException e){
System.out.println("认证失败:用户不存在");
e.printStackTrace();
}catch (IncorrectCredentialsException e){
System.out.println("认证失败:密码错误");
e.printStackTrace();
}catch (AuthenticationException e) {
e.printStackTrace();
}
}
}
验证通过
验证未通过,验证不存在的用户
(2)用户认证(单机应用)
这边采用三种加密方式,MD5、MD5+Salt和MD5+Salt+Hash,首先需要创建一个自定义realm,用于用户的验证与授权
public class CustomerRealm extends AuthorizingRealm {
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//获取身份信息
String principal = (String) token.getPrincipal();
//根据用户名查询数据库
if("zs".equals(principal)){
/**没有加盐salt的MD5:202cb962ac59075b964b07152d234b70**/
//return new SimpleAuthenticationInfo(principal,"202cb962ac59075b964b07152d234b70",this.getName());
/**md5+salt:243230d5403e2690008308353bd46f7e**/
// return new SimpleAuthenticationInfo(
// principal,//数据库查询到的用户名
// "243230d5403e2690008308353bd46f7e",//数据库MD5+salt之后的密码
// ByteSource.Util.bytes("q1v*(%"),//注册时的随机salt
// this.getName()//realm的名字
// );
/**md5+salt+hash:99c93a793708b521548aca28a226fd5b**/
return new SimpleAuthenticationInfo(
principal,//数据库查询到的用户名
"99c93a793708b521548aca28a226fd5b",//数据库MD5+salt之后的密码
ByteSource.Util.bytes("q1v*(%"),//注册时的随机salt
this.getName()//realm的名字
);
}
return null;
}
}
编写测试类
public class shiro02 {
public static void main(String[] args) {
//1.创建安全管理器对象
DefaultSecurityManager securityManager = new DefaultSecurityManager();
//2.给安全管理器设置realm
CustomerRealm customerRealm = new CustomerRealm();
//注入自定义realm
securityManager.setRealm(customerRealm);
//设置realm使用hash凭证匹配器
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
credentialsMatcher.setHashAlgorithmName("md5");//使用的加密算法
credentialsMatcher.setHashIterations(1024);//散列次数
customerRealm.setCredentialsMatcher(credentialsMatcher);
//3.securityManager给全局安全工具类设置安全管理器
SecurityUtils.setSecurityManager(securityManager);
//4.关键对象 subject 主体
Subject subject = SecurityUtils.getSubject();
//5.创建令牌
UsernamePasswordToken token = new UsernamePasswordToken();
token.setUsername("zs");//用户名
token.setPassword("123".toCharArray());//密码
//6.用户认证
try {
System.out.println("认证前状态:"+subject.isAuthenticated());
subject.login(token);
System.out.println("认证后状态:"+subject.isAuthenticated());
System.out.println("登录成功");
} catch (UnknownAccountException e){
System.out.println("认证失败:用户不存在");
e.printStackTrace();
}catch (IncorrectCredentialsException e){
System.out.println("认证失败:密码错误");
e.printStackTrace();
}catch (AuthenticationException e) {
e.printStackTrace();
}
}
}
shiro02 代码用了MD5+Salt+Hash,如果只需要用到MD5,则可以将salt和hash部分注释掉
(3)用户授权(单机应用)
用户的授权和认证都是在自定义的realm类中实现的,这里接着上面的(2)的案例,进行权限的授权,首先是自定义的realm类
public class CustomerRealm extends AuthorizingRealm {
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String primaryPrincipal = (String) principals.getPrimaryPrincipal();
System.out.println("身份信息:"+primaryPrincipal);
//根据身份信息、用户名、获取当前用户的角色信息,以及权限信息
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//将数据库中查询到的角色信息赋值给权限对象
simpleAuthorizationInfo.addRole("admin");
simpleAuthorizationInfo.addRole("user");
//将数据库中查询的权限信息赋值给权限对象
simpleAuthorizationInfo.addStringPermission("user:*:01");
simpleAuthorizationInfo.addStringPermission("product:create:*");
return simpleAuthorizationInfo;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//获取身份信息
String principal = (String) token.getPrincipal();
//根据用户名查询数据库
if("zs".equals(principal)){
/**没有加盐salt的MD5:202cb962ac59075b964b07152d234b70**/
//return new SimpleAuthenticationInfo(principal,"202cb962ac59075b964b07152d234b70",this.getName());
/**md5+salt:243230d5403e2690008308353bd46f7e**/
// return new SimpleAuthenticationInfo(
// principal,//数据库查询到的用户名
// "243230d5403e2690008308353bd46f7e",//数据库MD5+salt之后的密码
// ByteSource.Util.bytes("q1v*(%"),//注册时的随机salt
// this.getName()//realm的名字
// );
/**md5+salt+hash:99c93a793708b521548aca28a226fd5b**/
return new SimpleAuthenticationInfo(
principal,//数据库查询到的用户名
"99c93a793708b521548aca28a226fd5b",//数据库MD5+salt之后的密码
ByteSource.Util.bytes("q1v*(%"),//注册时的随机salt
this.getName()//realm的名字
);
}
return null;
}
}
测试用户授权
public class shiro02 {
public static void main(String[] args) {
//1.创建安全管理器对象
DefaultSecurityManager securityManager = new DefaultSecurityManager();
//2.给安全管理器设置realm
CustomerRealm customerRealm = new CustomerRealm();
//注入自定义realm
securityManager.setRealm(customerRealm);
//设置realm使用hash凭证匹配器
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
credentialsMatcher.setHashAlgorithmName("md5");//使用的加密算法
credentialsMatcher.setHashIterations(1024);//散列次数
customerRealm.setCredentialsMatcher(credentialsMatcher);
//3.securityManager给全局安全工具类设置安全管理器
SecurityUtils.setSecurityManager(securityManager);
//4.关键对象 subject 主体
Subject subject = SecurityUtils.getSubject();
//5.创建令牌
UsernamePasswordToken token = new UsernamePasswordToken();
token.setUsername("zs");//用户名
token.setPassword("123".toCharArray());//密码
//6.用户认证
try {
System.out.println("认证前状态:"+subject.isAuthenticated());
subject.login(token);
System.out.println("认证后状态:"+subject.isAuthenticated());
System.out.println("登录成功");
} catch (UnknownAccountException e){
System.out.println("认证失败:用户不存在");
e.printStackTrace();
}catch (IncorrectCredentialsException e){
System.out.println("认证失败:密码错误");
e.printStackTrace();
}catch (AuthenticationException e) {
e.printStackTrace();
}
//用户授权
if(subject.isAuthenticated()){
//基于角色权限控制
System.out.println(subject.hasRole("admin"));
//基于多角色权限控制,只有同时满足里面所有权限后才能访问
System.out.println(subject.hasAllRoles(Arrays.asList("admin", "user")));
//是否具有其中的一个角色
boolean[] booleans = subject.hasRoles(Arrays.asList("admin", "super", "user"));
for (boolean a : booleans) {
System.out.println(a);
}
//基于权限字符串的访问控制 资源标识符:操作:资源类型
System.out.println("权限:"+subject.isPermitted("user:update:01"));
System.out.println("权限:"+subject.isPermitted("product:create:02"));
//同时具有哪些权限
System.out.println(subject.isPermittedAll("user:*:01", "product:create:01"));
}
}
}
(4)完整demo
新建一个maven项目,并且引入shiro坐标
<!--shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.6.0</version>
</dependency>
为了更贴近实际的项目,这里特意编写了一个用户的业务层UserServiceImpl,模拟实际情况查询数据库
public class UserServiceImpl {
/**
* 根据用户名查询用户
* @param username
* @return
*/
public List<Map<String,String>> getUserByUsername(String username){
// 实际情况应该去查询数据库,这里就简单模拟一下,直接写死了用户名是zs
if("zs".equals(username)){
HashMap<String, String> user = new HashMap<String, String>() {{
put("username", "zs");
put("password", "6a15c7bc4b85a5cd8109c842be10d631");
put("salt", "qwer");
}};
return new ArrayList<Map<String,String>>(){{
add(user);
}};
}
return null;
}
/**
* 模拟根据用户名查询用户角色
* @param username
* @return
*/
public List<String> getRoleByUsername(String username){
// 模拟根据用户名查询用户角色
if("zs".equals(username)){
return new ArrayList<String>(){{
add("admin");
add("visitor");
}};
}
return null;
}
/**
* 根据角色名查询用户权限
* @param roleName
* @return
*/
public List<String> getPermByRole(String roleName){
// 模拟数据库根据角色名查询用户的权限
// 管理员有增删改查的权限
if("admin".equals(roleName)){
return new ArrayList<String>(){{
add("user:add");
add("user:detail");
add("user:edit");
add("user:del");
}};
}
// 游客只有查看的权限
if("visitor".equals(roleName)){
return new ArrayList<String>(){{
add("user:detail");
}};
}
return null;
}
}
这边采用三种加密方式,MD5+Salt+Hash,需要创建一个自定义realm,用于用户的验证与授权
算法:md5
salt:qwer
hash:1024
public class CustomerRealm extends AuthorizingRealm {
private UserServiceImpl userService = new UserServiceImpl();
/**
* 授权
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
/**
* 这里主要是查询该用户下的所有权限,步骤如下:
* (1)获取用户名
* (2)根据用户名获取角色
* (3)根据对应角色获取权限
* (4)所有角色的权限填充到”SimpleAuthorizationInfo“类中
*/
// 获取用户名
String username = (String) principalCollection.getPrimaryPrincipal();
//添加用户权限
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//根据用户名称查询对应角色
List<String> roles = userService.getRoleByUsername(username);
//依次添加权限
for (String roleName :roles){
//角色信息添加
simpleAuthorizationInfo.addRole(roleName);
List<String> perms = userService.getPermByRole(roleName);
//权限信息添加
for(String perm:perms){
simpleAuthorizationInfo.addStringPermission(perm);
}
}
return simpleAuthorizationInfo;
}
/**
* 认证
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
/**
* 这里主要是用户登录时进行登录认证,主要步骤如下:
* (1)获取登录用户的信息(姓名和密码)
* (2)先根据用户名判断该用户存不存在,不存在则抛出异常,然后在调用登录方法的地方进行异常捕捉即可
* (3)最后将查询出来的用户,交给”SimpleAuthenticationInfo“类进行登录认证即可
*/
// 获取用户的信息:UsernamePasswordToken,里面封装了username和password
UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken;
// 根据用户名查询该用户
List<Map<String, String>> users = userService.getUserByUsername(userToken.getUsername());
// 如果用户不存在就抛出异常
if(CollectionUtils.isEmpty(users)){
throw new RuntimeException("用户不存在");
}
//密码认证,shiro自动处理
return new SimpleAuthenticationInfo(
// 用户名
users.get(0).get("username"),
// 密码
users.get(0).get("password"),
// 随机盐值
ByteSource.Util.bytes(users.get(0).get("salt")),
// 固定写法(realm)
this.getName());
}
}
至此,用户的认证和授权的逻辑都在CustomerRealm里面实现了,下面开始测试
public class Main {
public static void main(String[] args) {
//1.创建安全管理器对象
DefaultSecurityManager securityManager = new DefaultSecurityManager();
//2.给安全管理器设置realm
CustomerRealm customerRealm = new CustomerRealm();
//注入自定义realm
securityManager.setRealm(customerRealm);
//设置realm使用hash凭证匹配器
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
//使用的加密算法
credentialsMatcher.setHashAlgorithmName("md5");
//散列次数
credentialsMatcher.setHashIterations(1024);
customerRealm.setCredentialsMatcher(credentialsMatcher);
//3.securityManager给全局安全工具类设置安全管理器
SecurityUtils.setSecurityManager(securityManager);
//4.关键对象 subject 主体
Subject subject = SecurityUtils.getSubject();
//5.创建令牌
/**
* 假设数据库存储用户信息是
* {
* username:"zs",
* password:"6a15c7bc4b85a5cd8109c842be10d631",
* salt:"qwer"
* }
*
* 明文密码123,经过"MD5+随机盐+散列次数(1024)"加密后的密文是:6a15c7bc4b85a5cd8109c842be10d631
*/
UsernamePasswordToken token = new UsernamePasswordToken();
//用户名
token.setUsername("zs");
//密码
token.setPassword("123".toCharArray());
//6.用户认证
try {
System.out.println("认证前状态:"+subject.isAuthenticated());
subject.login(token);
System.out.println("认证后状态:"+subject.isAuthenticated());
System.out.println("登录成功");
} catch (UnknownAccountException e){
System.out.println("认证失败:用户不存在");
e.printStackTrace();
}catch (IncorrectCredentialsException e){
System.out.println("认证失败:密码错误");
e.printStackTrace();
}catch (AuthenticationException e) {
e.printStackTrace();
}
//7.用户授权
if(subject.isAuthenticated()){
/**
* 有两种授权方式
* (1)基于角色进行授权
* (2)基于权限字符串进行授权
*/
//基于角色权限控制
System.out.println("是否具有admin用户权限:"+ subject.hasRole("admin"));
//基于多角色权限控制,只有同时满足里面所有权限后才能访问
System.out.println("是否同时具有admin和visitor用户权限:"+subject.hasAllRoles(Arrays.asList("admin", "visitor")));
//是否具有其中的一个角色
boolean[] booleans = subject.hasRoles(Arrays.asList("admin", "super", "user"));
System.out.print("是否具有'admin''super''user'其中的一个角色:");
for (boolean aBoolean : booleans) {
System.out.print(aBoolean+ "\t");
}
System.out.println();
//基于权限字符串的访问控制 资源标识符:操作:资源类型
System.out.println("是否具备有'user:detail'权限:"+subject.isPermitted("user:detail"));
System.out.println("是否具备有'user:edit'权限:"+subject.isPermitted("user:edit"));
System.out.println("是否具备有'user:del'权限:"+subject.isPermitted("user:del"));
System.out.println("是否具备有'user:add'权限:"+subject.isPermitted("user:add"));
System.out.println("是否具备有'user:update'权限:"+subject.isPermitted("user:update"));
//同时具有哪些权限
System.out.println("是否同时具备有'user:detail''user:add'权限:"+subject.isPermittedAll("user:detail", "user:add"));
}
}
}
如果把代码里面输入进行校验的用户名改成123456的话,就会报密码错误
token.setPassword("123456".toCharArray());
四、springboot整合
(1)认证与授权
首先引入依赖
<!--shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.2</version>
</dependency>
配置类ShiroConfig ,实现了拦截,所有的请求进来需要认证,里面需要写3个方法,ShiroFilterFactoryBean,DefaultWebSecurityManager和自定义Realm类,自定义Realm类在外面定义,然后在这个配置类里面new出来即可,DefaultWebSecurityManager需要使用自定义Realm类,ShiroFilterFactoryBean需要使用DefaultWebSecurityManager,一环套一环;然后里面有DefaultAdvisorAutoProxyCreator和AuthorizationAttributeSourceAdvisor,这两个用于支持开启Shiro的注解(如@RequiresRoles,@RequiresPermissions)
/**
* @ClassName : ShiroConfig
* @Description : Shiro配置类
* @Author : CJH
* @Date: 2020-09-02 09:35
*/
@Configuration
public class ShiroConfig {
/**
* ShiroFilterFactoryBean
*
* @param securityManager
* @return
*/
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//关联安全管理器(DefaultWebSecurityManager)
bean.setSecurityManager(securityManager);
/**
* 添加shiro的内置过滤器
* anon:无需认证就可以访问
* anthc:必须认证了才能访问
* user:必须拥有记住我功能才能用
* perms:拥有对某个资源的权限才能访问
* role:拥有对某个角色权限才能访问
*/
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/login", "anon");
filterMap.put("/register", "anon");
filterMap.put("/login_page", "anon");
filterMap.put("/auth/logout", "logout");//注销登录操作
//拦截所有的这一行必须放在最后
filterMap.put("/**", "authc");
bean.setFilterChainDefinitionMap(filterMap);//添加过滤器
bean.setLoginUrl("/login_page");//未登录时的跳转路径
return bean;
}
/**
* DefaultWebSecurityManager
*
* @param customerRealm
* @return
*/
@Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("customerRealm") CustomerRealm customerRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//关联UserRealm
securityManager.setRealm(customerRealm);
return securityManager;
}
/**
* 创建自定义realm类,自定义UserRealm
*
* @return
*/
@Bean(name = "customerRealm")
public CustomerRealm customerRealm() {
//修改凭证校验匹配器
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("MD5");//md5算法
hashedCredentialsMatcher.setHashIterations(1024);//散列次数
//应用凭证校验匹配器
CustomerRealm customerRealm = new CustomerRealm();
customerRealm.setCredentialsMatcher(hashedCredentialsMatcher);
return customerRealm;
}
/**
* 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions)
* 配置以下两个bean(DefaultAdvisorAutoProxyCreator和AuthorizationAttributeSourceAdvisor)即可实现此功能
*
* @return
*/
@Bean
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
自定义Realm类,该类用于实现用户的授权和认证
/**
* @ClassName : UserRealm
* @Description : shiro自定义的UserRealm类
* @Author : CJH
* @Date: 2020-09-02 09:45
*/
@Slf4j
public class CustomerRealm extends AuthorizingRealm {
@Autowired
private UserServer userServer;
@Autowired
private RoleServer roleServer;
@Autowired
private PermsServer permsServer;
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
log.info("授权");
String username = (String) principalCollection.getPrimaryPrincipal();
//添加用户权限
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
List<Role> roles = roleServer.queryRoleByUserName(username);//根据用户名称查询对应用户权限
//依次添加权限
for (Role role:roles){
//角色信息添加
simpleAuthorizationInfo.addRole(role.getName());
List<Perms> perms = permsServer.queryPermsByRoleId(role.getId());
//权限信息添加
for(Perms perm:perms){
simpleAuthorizationInfo.addStringPermission(perm.getName());
}
}
return simpleAuthorizationInfo;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
log.info("认证");
/**用户和密码认证**/
//用户名认证
UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken;//拿到token
List<User> users = userServer.queryUserByName(userToken.getUsername());
Assert.notEmpty(users,"用户不存在");
//密码认证,shiro自动处理
return new SimpleAuthenticationInfo(
users.get(0).getName(),
users.get(0).getPassword(),
ByteSource.Util.bytes(users.get(0).getSalt()),
this.getName());
}
}
编写测试控制器进行控制
@RestController
@RequestMapping("/test")
@RequiresRoles(value = {"test","admin"},logical = Logical.OR)
public class TestController {
@RequestMapping("/add")
public String add(){
return "this is test_add";
}
@RequestMapping("/update")
public String update(){
return "this is test_update";
}
@RequestMapping("/delete")
public String delete(){
return "this is test_delete";
}
@RequestMapping("/query")
public String query(){
return "this is test_query";
}
}
/**
* @ClassName : UserController
* @Description : 用户控制器
* @Author : CJH
* @Date: 2020-09-02 20:41
*/
@RestController
@RequestMapping("/user")
@RequiresRoles(value = {"user","admin"},logical = Logical.OR)//同时满足value里面所有的权限才能访问
public class UserController {
@RequestMapping("/add")
@RequiresPermissions("user:add")
public String add(){
return "this is user_add";
}
@RequestMapping("/update")
@RequiresPermissions("user:update")
public String update(){
return "this is user_update";
}
@RequestMapping("/delete")
@RequiresPermissions("user:delete")
public String delete(){
return "this is user_delete";
}
@RequestMapping("/query")
@RequiresPermissions("user:query")
public String query(){
return "this is user_query";
}
}
两个控制器用@RequiresRoles注解进行角色授权,@RequiresPermissions进行资源授权,只有对应的资源才有权限访问。
授权的方式有基于角色授权认证和基于资源授权认证,以上面的UserController为例,@RequiresRoles只有具有user角色的人才能访问,在该类里面,@RequiresPermissions只有具有对应的资源权限才能访问对应的接口,即接口级别的权限控制
(2)缓存
配置了上面的认证与授权之后发现,每次发起请求的时候,系统都会去数据库查询认证和授权信息,这样会影响效率,解决方案之一就是加个缓存,这样用户只有第一次发请求的时候需要查询数据,之后的验证信息都直接去查缓存
添加pom依赖
<!--shiro的ehcache缓存-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.6.0</version>
</dependency>
在配置类ShiroConfig里面的自定义reaml方法中增加缓存管理,下面给出整个完整的配置类
/**
* @ClassName : ShiroConfig
* @Description : Shiro配置类
* @Author : CJH
* @Date: 2020-09-02 09:35
*/
@Configuration
public class ShiroConfig {
/**
* ShiroFilterFactoryBean
* @param securityManager
* @return
*/
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//关联安全管理器(DefaultWebSecurityManager)
bean.setSecurityManager(securityManager);
/**
* 添加shiro的内置过滤器
* anon:无需认证就可以访问
* anthc:必须认证了才能访问
* user:必须拥有记住我功能才能用
* perms:拥有对某个资源的权限才能访问
* role:拥有对某个角色权限才能访问
*/
Map<String,String> filterMap = new LinkedHashMap<>();
filterMap.put("/login","anon");
filterMap.put("/register","anon");
filterMap.put("/login_page","anon");
filterMap.put("/**","authc");
bean.setFilterChainDefinitionMap(filterMap);//添加过滤器
bean.setLoginUrl("/login_page");//未登录时的跳转路径
return bean;
}
/**
* DefaultWebSecurityManager
* @param customerRealm
* @return
*/
@Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("customerRealm") CustomerRealm customerRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//关联UserRealm
securityManager.setRealm(customerRealm);
return securityManager;
}
/**
* 创建自定义realm类,自定义UserRealm
* @return
*/
@Bean(name = "customerRealm")
public CustomerRealm customerRealm(){
//修改凭证校验匹配器
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("MD5");//md5算法
hashedCredentialsMatcher.setHashIterations(1024);//散列次数
CustomerRealm customerRealm = new CustomerRealm();
//应用凭证校验匹配器
customerRealm.setCredentialsMatcher(hashedCredentialsMatcher);
//开启缓存
customerRealm.setCacheManager(new EhCacheManager());
customerRealm.setCachingEnabled(true);
customerRealm.setAuthenticationCachingEnabled(true);//开启认证缓存
customerRealm.setAuthenticationCacheName("authenticationCache");//给认证缓存起个名字
customerRealm.setAuthorizationCachingEnabled(true);//开启授权缓存
customerRealm.setAuthorizationCacheName("authorizationCache");//给授权缓存起个名字
return customerRealm;
}
/**
* 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions)
* 配置以下两个bean(DefaultAdvisorAutoProxyCreator和AuthorizationAttributeSourceAdvisor)即可实现此功能
* @return
*/
@Bean
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}