黑马程序员-由浅入深掌握Shiro权限框架(3)Shiro入门

目录

第三章 Shiro入门

1、身份认证

【1】基本流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6bC7JDS9-1642857208825)(image
80047247677.png)]

流程如下:

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】实现
【2.2.1】新建项目

HelloShiro

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gqvgchMG-1642857208826)(image
80048751209.png)]

【2.2.2】导入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>HelloShiro</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <dependencies>

        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.1.3</version>
        </dependency>

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.3.2</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <!-- compiler插件, 设定JDK版本 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                    <showWarnings>true</showWarnings>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
【2.2.3】编写shiro.ini
#声明用户账号
[users]
jay=123
【2.2.4】编写HelloShiro
package com.xh.shiro;

import org.apache.shiro.SecurityUtils;
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;
import org.junit.Test;

/**
 * @Description:shiro的第一个例子
 */
public class HelloShiro {

    @Test
    public void shiroLogin() {
        //导入权限ini文件构建权限工厂
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        //工厂构建安全管理器
        SecurityManager securityManager = factory.getInstance();
        //使用SecurityUtils工具生效安全管理器
        SecurityUtils.setSecurityManager(securityManager);
        //使用SecurityUtils工具获得主体
        Subject subject = SecurityUtils.getSubject();
        //构建账号token
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("jay", "123");
        //登录操作
        subject.login(usernamePasswordToken);
        System.out.println("是否登录成功:" + subject.isAuthenticated());
    }
}

【2.2.4】测试

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gHWrUFHh-1642857208826)(image
80049504816.png)]

【2.3】小结
1、权限定义:ini文件
2、加载过程:
	导入权限ini文件构建权限工厂
	工厂构建安全管理器
	使用SecurityUtils工具生效安全管理器
	使用SecurityUtils工具获得主体
	使构建账号token用SecurityUtils工具获得主体
	构建账号token
	登录操作

2、Realm

【1】Realm接口

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ypiRLmGz-1642857208827)(image
80050317405.png)]
所以,一般在真实的项目中,我们不会直接实现Realm接口,我们一般的情况就是直接继承AuthorizingRealm,能够继承到认证与授权功能。它需要强制重写两个方法

package com.xh.shiro.realm;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

public class DefinitionRealm extends AuthorizingRealm {
    /**
     * 鉴权
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    /**
     * 认证
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        return null;
    }
}
【2】自定义Realm
【2.1】需求
1、自定义Realm,取得密码用于比较
【2.2】实现
【2.2.1】创建项目

HelloShiro,在原有项目基础上进行修改即可。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AxEYN5ap-1642857208828)(image
80107180701.png)]

【2.2.2】定义SecurityService

SecurityService

package com.xh.shiro.service;

/**
 * @Description:权限服务接口
 */
public interface SecurityService {
    /**
     * @Description 查找密码按用户登录名
     * @param loginName 登录名称
     * @return
     */
    String findPasswordByLoginName(String loginName);
}

SecurityServiceImpl

package com.xh.shiro.service.impl;

import com.xh.shiro.service.SecurityService;

public class SecurityServiceImpl implements SecurityService {
    @Override
    public String findPasswordByLoginName(String loginName) {
        if (!"jay".equals(loginName)){
            return null;
        }
        return "123";
    }
}
【2.2.3】定义DefinitionRealm
package com.xh.shiro.realm;

import com.xh.shiro.service.SecurityService;
import com.xh.shiro.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 DefinitionRealm extends AuthorizingRealm {
    /**
     * @Description 认证接口
     * @param token 传递登录token
     * @return
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        // 从AuthenticationToken中获得登录名称
        String loginName = (String) token.getPrincipal();
        SecurityService securityService = new SecurityServiceImpl();
        String password = securityService.findPasswordByLoginName(loginName);
        if ("".equals(password)||password==null){
            throw new UnknownAccountException("账户不存在");
        }
        // 传递账号和密码 getName()在此处是指明认证所使用的realm
        return  new SimpleAuthenticationInfo(loginName,password,getName());
    }


    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }
}
【2.2.4】编辑shiro.ini
#声明用户账号
;[users]
;jay=123
#声明自定义的realm,且为安全管理器指定realms
[main]
definitionRealm=com.xh.shiro.realm.DefinitionRealm
securityManager.realms=$definitionRealm

上面的; 是在idea中install 了ini文件插件,进行注释的时候,展示的注释符号,知道即可。

进行测试:
在这里插入图片描述
当账号和密码提供错误时,会分别报一下异常:
在这里插入图片描述

在这里插入图片描述

【3】成功认证源码跟踪

在这里插入图片描述

(1)通过debug模式追踪源码subject.login(token) 发现。首先是进入Subject接口的默认实现类。果然,Subject将用户的用户名密码委托给了securityManager去做。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oARVKQYe-1642857208828)(image
80107715689.png)]

(2)然后,securityManager说:“卧槽,认证器authenticator小弟,听说你的大学学的专业就是认证呀,那么这个认证的任务就交给你咯”。遂将用户的token委托给内部认证组件authenticator去做

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HRLpNyNV-1642857208829)(image
80107767167.png)]

(3)事实上,securityManager的内部组件一个比一个懒。内部认证组件authenticator说:“你们传过来的token我需要拿去跟数据源Realm做对比,这样吧,这个光荣的任务就交给Realm你去做吧”。Realm对象:“一群大懒虫!”。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VaVRdkaQ-1642857208830)(image
80107849133.png)]

(4)Realm在接到内部认证组件authenticator组件后很伤心,最后对电脑前的你说:“大兄弟,对不住了,你去实现一下呗”。从图中的方法体中可以看到,当前对象是Realm类对象,即将调用的方法是doGetAuthenticationInfo(token)。而这个方法,就是你即将要重写的方法。如果帐号密码通过了,那么返回一个认证成功的info凭证。如果认证失败,抛出一个异常就好了。你说:“什么最终还是劳资来认证?”没错,就是苦逼的你去实现了,谁叫你是程序猿呢。所以,你不得不查询一下数据库,重写doGetAuthenticationInfo方法,查出来正确的帐号密码,返回一个正确的凭证info
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MHdfO6io-1642857208830)(image
80108004686.png)]

(5)好了,这个时候你自己编写了一个类,继承了AuthorizingRealm,并实现了上述doGetAuthenticationInfo方法。你在doGetAuthenticationInfo中编写了查询数据库的代码,并将数据库中存放的用户名与密码封装成了一个AuthenticationInfo对象返回。可以看到下图中,info这个对象是有值的,说明从数据库中查询出来了正确的帐号密码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zuwnuOSj-1642857208831)(image
80108049930.png)]

(6)那么,接下来就很简单了。把用户输入的帐号密码与刚才你从数据库中查出来的帐号密码对比一下即可。token封装着用户的帐号密码,AuthenticationInfo封装着从数据库中查询出来的帐号密码。再往下追踪一下代码,最终到了下图中的核心区域。如果没有报异常,说明本次登录成功。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5xWQyKFi-1642857208831)(image
80192296050.png)]

3、编码、散列算法

【1】编码与解码

Shiro提供了base64和16进制字符串编码/解码的API支持,方便一些编码解码操作。

Shiro内部的一些数据的【存储/表示】都使用了base64和16进制字符串

【1.1】需求
理解base64和16进制字符串编码/解码
【1.2】新建项目

HelloShiro

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-39nzQIO5-1642857208832)(image
80196809749.png)]

【1.3】新建EncodesUtil
package com.xh.shiro.utils;

import org.apache.shiro.codec.Base64;
import org.apache.shiro.codec.Hex;

/**
 * @Description:封装base64和16进制编码解码工具类
 */
public class EncodesUtil {

    /**
     * @Description HEX-byte[]--String转换
     * @param input 输入数组
     * @return String
     */
    public static String encodeHex(byte[] input){
        return Hex.encodeToString(input);
    }

    /**
     * @Description HEX-String--byte[]转换
     * @param input 输入字符串
     * @return byte数组
     */
    public static byte[] decodeHex(String input){
        return Hex.decode(input);
    }

    /**
     * @Description Base64-byte[]--String转换
     * @param input 输入数组
     * @return String
     */
    public static String encodeBase64(byte[] input){
        return Base64.encodeToString(input);
    }

    /**
     * @Description Base64-String--byte[]转换
     * @param input 输入字符串
     * @return byte数组
     */
    public static byte[] decodeBase64(String input){
        return Base64.decode(input);
    }
}
【1.4】新建TestEncodesUtil
package com.xh.shiro;

import com.xh.shiro.utils.EncodesUtil;
import org.junit.Test;

public class TestEncodesUtil {


    /**
     * @Description 测试16进制编码
     */
    @Test
    public void testHex() {
        String val = "holle";
        String flag = EncodesUtil.encodeHex(val.getBytes());
        System.out.println("flag: "+ flag);
        String valHandler = new String(EncodesUtil.decodeHex(flag));
        System.out.println("valHandler: valHandler");
        System.out.println("比较结果:" + val.equals(valHandler));
    }

    /**
     * @Description 测试base64编码
     */
    @Test
    public void testBase64() {
        String val = "holle";
        String flag = EncodesUtil.encodeBase64(val.getBytes());
        System.out.println("flag: "+ flag);
        String valHandler = new String(EncodesUtil.decodeBase64(flag));
        System.out.println("valHandler: valHandler");
        System.out.println("比较结果:" + val.equals(valHandler));
    }
}

【1.5】小结

1、shiro目前支持的编码与解码:
	base64
   (HEX)16进制字符串
2、那么shiro的编码与解码什么时候使用呢?又是怎么使用的呢?
【2】散列算法

散列算法一般用于生成数据的摘要信息,是一种不可逆的算法,一般适合存储密码之类的数据,常见的散列算法如MD5、SHA等。一般进行散列时最好提供一个salt(盐),比如加密密码“admin”,产生的散列值是“21232f297a57a5a743894a0e4a801fc3”,可以到一些md5解密网站很容易的通过散列值得到密码“admin”,即如果直接对密码进行散列相对来说破解更容易,此时我们可以加一些只有系统知道的干扰数据,如salt(即盐);这样散列的对象是“密码+salt”,这样生成的散列值相对来说更难破解。

shiro支持的散列算法:

Md2Hash、Md5Hash、Sha1Hash、Sha256Hash、Sha384Hash、Sha512Hash

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3w9mPswZ-1642857208832)(image
80277706466.png)]

【2.1】新增DigestsUtil
package com.xh.shiro.utils;

import org.apache.shiro.crypto.SecureRandomNumberGenerator;
import org.apache.shiro.crypto.hash.SimpleHash;

import java.util.HashMap;
import java.util.Map;

/**
 * @Description:摘要
 */
public class DigestsUtil {

    public static final String SHA1 = "SHA-1"; // 固定写法,算法

    public static final Integer ITERATIONS =512; // 加密次数

    /**
     * @Description sha1方法
     * @param input 需要散列字符串
     * @param salt 盐字符串
     * @return
     */
    public static String sha1(String input, String salt) {
        return new SimpleHash(SHA1, input, salt,ITERATIONS).toString();
    }

    /**
     * @Description 随机获得salt字符串
     * @return
     */
    public static String generateSalt(){
        SecureRandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator();
        return randomNumberGenerator.nextBytes().toHex();
    }


    /**
     * @Description 生成密码字符密文和salt密文
     * @param
     * @return
     */
    public static Map<String,String> entryptPassword(String passwordPlain) {
        Map<String,String> map = new HashMap<>();
        String salt = generateSalt();
        String password =sha1(passwordPlain,salt);
        map.put("salt", salt);
        map.put("password", password);
        return map;
    }
}
【2.2】新增TestDigestsUtil
package com.xh.shiro;

import com.xh.shiro.utils.DigestsUtil;
import com.xh.shiro.utils.EncodesUtil;
import org.junit.Test;

import java.util.Map;

/**
 * @Description:测试
 */
public class TestDigestsUtil {


    @Test
    public void testDigestsUtil(){
        for (int i = 0; i < 5; i++) {
            Map<String,String> map =  DigestsUtil.entryptPassword("123");
            System.out.println("获得结果:"+map.toString());
            // 因为每次的盐是随机生成的,所以一样的明文密码也会得到不同的暗文。
            //获得结果:{password=d5177abac5df1a8dfcf42112bd90729beeb81ffe, salt=4d120adc5eee51a5bed31bd909d2347d}
            //获得结果:{password=389a690d885398b2f4f58047f3220d4b37f04e76, salt=19fa1d42462058ec4e9d9f3a64911926}
            //获得结果:{password=bf8c9d80a7fa5055ce929057c157e1c4086cc4c5, salt=9d5190b2b5f2ebfa8181d2b5454f58db}
            //获得结果:{password=846af79f48495cb6a3ded8ec922ca8bedef632d0, salt=392e466d22a0739c600163750aeeb593}
            //获得结果:{password=f3a49da17f60bf310ef60c9a9662af131e750929, salt=27475706eb47ccaf3abead97b1eaf7b7}
        }
    }

}

4、Realm使用散列算法

上面我们了解编码,以及散列算法,那么在realm中怎么使用?在之前中我们使用的密码是明文的校验方式,也就是SecurityServiceImpl中findPasswordByLoginName返回的是明文123的密码。

【1】新建项目

HelloShiro

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LIoacStY-1642857208833)(image
80281644492.png)]

【2】创建密文密码 这一步没什么用处。

使用ClientTest的testDigestsUtil创建密码为“123”的password密文和salt密文

【3】修改SecurityService

SecurityService修改成返回salt和password的map

package com.xh.shiro.service;

import java.util.Map;

/**
 * @Description:权限服务接口
 */
public interface SecurityService {
    /**
     * @Description 查找密码按用户登录名
     * @param loginName 登录名称
     * @return
     */
    Map<String,String> findPasswordByLoginName(String loginName);
}



package com.xh.shiro.service.impl;

import com.xh.shiro.service.SecurityService;
import com.xh.shiro.utils.DigestsUtil;

import java.util.Map;

public class SecurityServiceImpl implements SecurityService {
    @Override
    public Map<String,String> findPasswordByLoginName(String loginName) {
        if (!"jay".equals(loginName)){
            return null;
        }
        //模拟数据库中存储的密文信息
        return  DigestsUtil.entryptPassword("123");
    }
}
【4】指定密码匹配方式

1)为DefinitionRealm类添加构造方法DefinitionRealm(),指定账号密码的相关算法配置。
2)修改认证doGetAuthenticationInfo方法

	package com.xh.shiro.realm;

import com.xh.shiro.service.SecurityService;
import com.xh.shiro.service.impl.SecurityServiceImpl;
import com.xh.shiro.utils.DigestsUtil;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;

import java.util.Map;

public class DefinitionRealm extends AuthorizingRealm {
    /**
     * @Description 认证接口
     * @param token 传递登录token
     * @return
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        // 从AuthenticationToken中获得登录名称
        String loginName = (String) token.getPrincipal();
        // 从数据库中获得账号信息
        SecurityService securityService = new SecurityServiceImpl();
        Map<String,String> map= securityService.findPasswordByLoginName(loginName);
        if (map.isEmpty()){
            throw new UnknownAccountException("账户不存在");
        }
        String salt = map.get("salt");
        String password = map.get("password");
        // 传递账号和密码 getName()在此处是指明认证所使用的realm
        // 传递账号和密码:参数1:缓存对象,参数2:明文密码,参数三:字节salt,参数4:当前DefinitionRealm名称
        return  new SimpleAuthenticationInfo(loginName,password, ByteSource.Util.bytes(salt),getName());
    }

    /**
     * @Description 构造函数,指定散列算法和加密次数,并设置到匹配方式里面,用于最后token中账号密码和数据库中账号密码的比对
     */
    public DefinitionRealm() {
        //指定密码匹配方式为sha1
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(DigestsUtil.SHA1);
        //指定密码迭代次数
        matcher.setHashIterations(DigestsUtil.ITERATIONS);
        //使用父亲方法使匹配方式生效
        setCredentialsMatcher(matcher);
    }
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }
}
【5】测试

在这里插入图片描述

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9h8ckKLc-1642857208833)(image
80282723275.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-opiGoo6m-1642857208834)(image
80282829816.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yVJR17c8-1642857208834)(image
80282869315.png)]

5、身份授权

【1】基本流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6BHI15qq-1642857208834)(image
80284991759.png)]

1、首先调用Subject.isPermitted/hasRole接口,其会委托给SecurityManager。

2、SecurityManager接着会委托给内部组件Authorizer;

3、Authorizer再将其请求委托给我们的Realm去做;Realm才是真正干活的;

4、Realm将用户请求的参数封装成权限对象。再从我们重写的doGetAuthorizationInfo方法中获取从数据库中查询到的权限集合。

5、Realm将用户传入的权限对象,与从数据库中查出来的权限对象,进行一一对比。如果用户传入的权限对象在从数据库中查出来的权限对象中,则返回true,否则返回false。

进行授权操作的前提:用户必须通过认证。

在真实的项目中,角色与权限都存放在数据库中。为了快速上手,我们先创建一个自定义DefinitionRealm,模拟它已经登录成功。直接返回一个登录验证凭证,告诉Shiro框架,我们从数据库中查询出来的密码是也是就是你输入的密码。所以,不管用户输入什么,本次登录验证都是通过的。

  /**
     * @Description 认证接口
     * @param token 传递登录token
     * @return
     */
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    //从AuthenticationToken中获得登录名称
    String loginName = (String) token.getPrincipal();
    SecurityService securityService = new SecurityServiceImpl();
    Map<String, String> map = securityService.findPasswordByLoginName(loginName);
    if (map.isEmpty()){
        throw new UnknownAccountException("账户不存在");
    }
    String salt = map.get("salt");
    String password = map.get("password");
    //传递账号和密码:参数1:用户认证凭证信息,参数2:明文密码,参数三:字节salt,参数4:当前DefinitionRealm名称
    return  new SimpleAuthenticationInfo(loginName,password, ByteSource.Util.bytes(salt),getName());
}

好了,接下来,我们要重写我们本小节的核心方法了。在DefinitionRealm中找到下列方法:

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    return null;
}

此方法的传入的参数PrincipalCollection principals,是一个包装对象,它表示"用户认证凭证信息"。包装的是谁呢?没错,就是认证doGetAuthenticationInfo()方法的返回值的第一个参数loginName。你可以通过这个包装对象的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】创建项目

HelloShiro

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-perQKt9u-1642857208835)(image
80367506898.png)]

【2.2.2】编写SecurityService

在SecurityService中添加

 	 /**
     * @Description 查找角色按用户登录名
     * @param  loginName 登录名称
     * @return
     */
    List<String> findRoleByloginName(String loginName);

    /**
     * @Description 查找资源按用户登录名
     * @param  loginName 登录名称
     * @return
     */
    List<String>  findPermissionByloginName(String loginName);

SecurityServiceImpl添加实现

@Override
public List<String> findRoleByloginName(String loginName) {
    List<String> list = new ArrayList<>();
    list.add("admin");
    list.add("dev");
    return list;
}

@Override
public List<String>  findPermissionByloginName(String loginName) {
    List<String> list = new ArrayList<>();
    list.add("order:add");
    list.add("order:list");
    list.add("order:del");
    return list;
}
【2.2.3】编写DefinitionRealm

在DefinitionRealm中修改doGetAuthorizationInfo方法如下

/**
  * @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.4】编写HelloShiro
package com.xh.shiro;

import org.apache.shiro.SecurityUtils;
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;
import org.junit.Test;

/**
 * @Description:shiro的第一个例子
 */
public class HelloShiro {

    public Subject shiroLogin(String loginName, String password) {
        //导入权限ini文件构建权限工厂
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        //工厂构建安全管理器
        SecurityManager securityManager = factory.getInstance();
        //使用SecurityUtils工具生效安全管理器
        SecurityUtils.setSecurityManager(securityManager);
        //使用SecurityUtils工具获得主体
        Subject subject = SecurityUtils.getSubject();
        //构建账号token
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(loginName, password);
        //登录操作
        subject.login(usernamePasswordToken);
        return subject;
    }


    @Test
    public void testPermissionRealm() {
        Subject subject = shiroLogin("jay", "123");
        //判断用户是否已经登录
        System.out.println("是否登录成功:" + subject.isAuthenticated());

        //---------检查当前用户的角色信息------------
        System.out.println("是否有管理员角色:"+subject.hasRole("admin"));
        //---------如果当前用户有此角色,无返回值。若没有此权限,则抛 UnauthorizedException------------
        try {
            subject.checkRole("coder");
            System.out.println("有coder角色");
        }catch (Exception e){
            System.out.println("没有coder角色");
        }

        //---------检查当前用户的权限信息------------
        System.out.println("是否有查看订单列表资源:"+subject.isPermitted("order:list"));
        //---------如果当前用户有此权限,无返回值。若没有此权限,则抛 UnauthorizedException------------
        try {
            subject.checkPermissions("order:add", "order:del");
            System.out.println("有添加和删除订单资源");
        }catch (Exception e){
            System.out.println("没有有添加和删除订单资源");
        }

    }
}
【2.3】授权源码追踪

(1)客户端调用 subject.hasRole(“admin”),判断当前用户是否有"admin"角色权限。

在这里插入图片描述

(2)Subject门面对象接收到要被验证的角色信息"admin",并将其委托给securityManager中验证。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j5sWeupJ-1642857208835)(image
80368995672.png)]

(3)securityManager将验证请求再次委托给内部的小弟:内部组件Authorizer authorizer

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0y6Hh7JD-1642857208836)(image
80369087293.png)]

(4)内部小弟authorizer也是个混子,将其委托给了我们自定义的Realm去做

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yOf9e7VG-1642857208836)(image
80369205937.png)]

(5) 先拿到PrincipalCollection principal对象,同时传入校验的角色循环校验,循环中先创建鉴权信息

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vFhk1F1H-1642857208836)(image
80369895496.png)]

(6)先看缓存中是否已经有鉴权信息

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ESd3lrvU-1642857208836)(image
80369979541.png)]
在这里插入图片描述

(7)都是一群懒货!!最后干活的还是我这个猴子!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-itM1huAL-1642857208837)(image
80370172670.png)]

【3】小结
1、鉴权需要实现doGetAuthorizationInfo方法
2、鉴权使用门面subject中方法进行鉴权
	以check开头的会抛出异常
	以is和has开头会返回布尔值
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值