Shiro核心概念

官网

http://shiro.apache.org/introduction.html

http://shiro.apache.org/architecture.html

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J2rdihpo-1586338243922)(http://tuchuang.zhangln.com/h6PW9V.png)]

核心概念

身份认证:Authentication

  • Authentication: Sometimes referred to as ‘login’, this is the act of proving a user is who they say they are.

    一般指登陆

授权:Authorization

  • Authorization: The process of access control, i.e. determining ‘who’ has access to ‘what’.

    给用户分配角色,或者访问资源的权限

会话管理:Session Manager

  • Session Management: Managing user-specific sessions, even in non-web or EJB applications.

    一般指WebSession

SessionManager (org.apache.shiro.session.mgt.SessionManager)
The SessionManager knows how to create and manage user Session lifecycles to provide a robust Session experience for users in all environments. This is a unique feature in the world of security frameworks - Shiro has the ability to natively manage user Sessions in any environment, even if there is no Web/Servlet or EJB container available. By default, Shiro will use an existing session mechanism if available, (e.g. Servlet Container), but if there isn’t one, such as in a standalone application or non-web environment, it will use its built-in enterprise session management to offer the same programming experience. The SessionDAO exists to allow any datasource to be used to persist sessions.

  • SessionDAO (org.apache.shiro.session.mgt.eis.SessionDAO)
    The SessionDAO performs Session persistence (CRUD) operations on behalf of the SessionManager. This allows any data store to be plugged in to the Session Management infrastructure.

加解密:Cryptography

  • Cryptography: Keeping data secure using cryptographic algorithms while still being easy to use.

数据的加解密

Cryptography (org.apache.shiro.crypto.*)
Cryptography is a natural addition to an enterprise security framework. Shiro’s crypto package contains easy-to-use and understand representations of crytographic Ciphers, Hashes (aka digests) and different codec implementations. All of the classes in this package are carefully designed to be very easy to use and easy to understand. Anyone who has used Java’s native cryptography support knows it can be a challenging animal to tame. Shiro’s crypto APIs simplify the complicated Java mechanisms and make cryptography easy to use for normal mortal human beings.

主体:Subject

Subject (org.apache.shiro.subject.Subject)
A security-specific ‘view’ of the entity (user, 3rd-party service, cron job, etc) currently interacting with the software.

安全管理器:SecurityManager

SecurityManager (org.apache.shiro.mgt.SecurityManager)
As mentioned above, the SecurityManager is the heart of Shiro’s architecture. It is mostly an ‘umbrella’ object that coordinates its managed components to ensure they work smoothly together. It also manages Shiro’s view of every application user, so it knows how to perform security operations per user.

数据域:Realm

Realms (org.apache.shiro.realm.Realm)
As mentioned above, Realms act as the ‘bridge’ or ‘connector’ between Shiro and your application’s security data. When it comes time to actually interact with security-related data like user accounts to perform authentication (login) and authorization (access control), Shiro looks up many of these things from one or more Realms configured for an application. You can configure as many Realms as you need (usually one per data source) and Shiro will coordinate with them as necessary for both authentication and authorization.

认证器:Authenticator

Authenticator (org.apache.shiro.authc.Authenticator)
The Authenticator is the component that is responsible for executing and reacting to authentication (log-in) attempts by users. When a user tries to log-in, that logic is executed by the Authenticator. The Authenticator knows how to coordinate with one or more Realms that store relevant user/account information. The data obtained from these Realms is used to verify the user’s identity to guarantee the user really is who they say they are.

  • Authentication Strategy (org.apache.shiro.authc.pam.AuthenticationStrategy)
    If more than one Realm is configured, the AuthenticationStrategy will coordinate the Realms to determine the conditions under which an authentication attempt succeeds or fails (for example, if one realm succeeds but others fail, is the attempt successful? Must all realms succeed? Only the first?).

授权器:Authorizer

Authorizer (org.apache.shiro.authz.Authorizer)
The Authorizer is the component responsible determining users’ access control in the application. It is the mechanism that ultimately says if a user is allowed to do something or not. Like the Authenticator, the Authorizer also knows how to coordinate with multiple back-end data sources to access role and permission information. The Authorizer uses this information to determine exactly if a user is allowed to perform a given action.

缓存管理器:Cache Manager

CacheManager (org.apache.shiro.cache.CacheManager)
The CacheManager creates and manages Cache instance lifecycles used by other Shiro components. Because Shiro can access many back-end data sources for authentication, authorization and session management, caching has always been a first-class architectural feature in the framework to improve performance while using these data sources. Any of the modern open-source and/or enterprise caching products can be plugged in to Shiro to provide a fast and efficient user-experience.

HelloWorld

认证流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k4JXZNRv-1586338243923)(http://tuchuang.zhangln.com/bli4dG.png)]

基本案例演示

package com.example.demo1;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.SimpleAccountRealm;
import org.apache.shiro.subject.Subject;
import org.junit.Before;
import org.junit.Test;

/**
 * @author sherry
 * @description
 * @date Create in 2020/4/6
 * @modified By:
 */

public class QuickStartTest {

    private SimpleAccountRealm accountRealm = new SimpleAccountRealm();

    /**
     * 初始化SecurityManager
     */
    private DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();

    @Before
    public void init() {
        /**
         * 初始化Realm
         */
        accountRealm.addAccount("admin", "123456");
        accountRealm.addAccount("admin1", "1234567");
        /**
         * 构建SecurityManager
         */
        defaultSecurityManager.setRealm(accountRealm);
    }

    /**
     * 测试认证
     */
    @Test
    public void test1Authentication() {
        SecurityUtils.setSecurityManager(defaultSecurityManager);

//        获取当前主体
        Subject subject = SecurityUtils.getSubject();

//        构造认证信息
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("admin", "123456");
//        登陆认证
        subject.login(usernamePasswordToken);

        System.out.println("认证结果:" + subject.isAuthenticated());//true

        System.out.println("当前用户是否有某个角色:" + subject.hasRole("root"));
        System.out.println("subject别名:" + subject.getPrincipal());

//        不同于subject.hasRole("root"),校验失败的话会抛出异常
        try {
            subject.checkRole("root");
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
//        退出登录
        subject.logout();

    }
}

完整源码

我们分析一下,在这段代码中,各个组件起到的作用

  • Realm:SimpleAccountRealm

数据域,注册了一批合法的用户,供SecurityManager使用

  • SecurityManager:DefaultSecurityManager

安全管理器,执行安全相关的操作

  • SecurityUtils.setSecurityManager(defaultSecurityManager)

将当前安全环境进行绑定(其实是绑定到了当前线程)

  • Subject subject = SecurityUtils.getSubject()

获取到当前线程在访问的用户主体(没有的话,会新建一个)

授权流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DE1XZ84n-1586338243923)(http://tuchuang.zhangln.com/SSP77c.png)]

基本案例代码

package com.example.demo1;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.SimpleAccountRealm;
import org.apache.shiro.subject.Subject;
import org.junit.Before;
import org.junit.Test;

/**
 * @author sherry
 * @description
 * @date Create in 2020/4/6
 * @modified By:
 */

public class QuickStartTest {

    private SimpleAccountRealm accountRealm = new SimpleAccountRealm();

    /**
     * 初始化SecurityManager
     */
    private DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();

    @Before
    public void init() {
        /**
         * 初始化Realm
         */
        accountRealm.addAccount("admin", "123456","root");
        accountRealm.addAccount("admin1", "1234567");
        /**
         * 构建SecurityManager
         */
        defaultSecurityManager.setRealm(accountRealm);
    }

    /**
     * 测试认证
     */
    @Test
    public void test1Authentication() {
        SecurityUtils.setSecurityManager(defaultSecurityManager);

//        获取当前主体
        Subject subject = SecurityUtils.getSubject();

//        构造认证信息
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("admin", "123456");
//        登陆认证
        subject.login(usernamePasswordToken);

        System.out.println("认证结果:" + subject.isAuthenticated());//true

        System.out.println("当前用户是否有某个角色:" + subject.hasRole("root"));
        System.out.println("subject别名:" + subject.getPrincipal());

//        不同于subject.hasRole("root"),校验失败的话会抛出异常
        try {
            subject.checkRole("root");
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
//        退出登录
        subject.logout();

    }
}

与认证的案例代码基本 一致,唯一的区别在于构建Realm的时候,设置了一下用户对应的角色

Realm详解

Realm保存了完整的安全信息,什么用户有什么角色,什么角色有什么权限。

Shiro全部的认证授权动作,最终都需要经过Realm

  • Realm的实现类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w01EmlWi-1586338243924)(http://tuchuang.zhangln.com/5MAVRh.png)]

  • 选择其中的JdbcRealm,他的继承体系如下

实际开发中往往自定义Realm,并且通过继承AuthorizingRealm进行实现

IniRealm

IniRealm,使用ini文件来描述角色权限关系

  • ini文件举例
# 配置用户及其角色
# 格式  用户名=密码,角色1,角色2,...角色n
[users]

admin=123456,user


# 配置角色及其权限
# 格式 角色名=权限1,权限2,...权限n
# 角色可以使用通配符,如 admin=*,就表示admin角色拥有所有权限,user=video:*,表示user角色拥有对video的全部操作权限
[roles]

user=video:find,video:buy
  • 使用举例
package com.example.demo1;

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;
import org.junit.platform.engine.support.descriptor.ClassSource;

/**
 * @author sherry
 * @description
 * @date Create in 2020/4/7
 * @modified By:
 */

public class IniRealmTest {
    @Test
    public void testAuthentication() {
//        创建SecurityManager工程,通过配置文件ini创建

        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        SecurityManager securityManager = factory.getInstance();

//        将SecurityManager设置到当前运行环境中
        SecurityUtils.setSecurityManager(securityManager);

        Subject subject = SecurityUtils.getSubject();
        //        构造认证信息
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("admin", "123456");
//        登陆认证
        subject.login(usernamePasswordToken);

        System.out.println("认证结果:" + subject.isAuthenticated());//true

        System.out.println("当前用户是否有root角色:" + subject.hasRole("root"));
        System.out.println("当前用户是否有user角色:" + subject.hasRole("user"));


        System.out.println("当前用户是否有video:buy权限" + subject.isPermitted("video:buy"));


        System.out.println("subject别名:" + subject.getPrincipal());

//        不同于subject.hasRole("root"),校验失败的话会抛出异常
        try {
            subject.checkRole("root");
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
//        退出登录
        subject.logout();

    }
}

JdbcRealm

在IniRealm中,用户、角色、权限的信息,保存在ini文件中,

相应的,JdbcRealm中,默认情况下,用户、角色、权限信息,保存在users、user_roles、roles_permissions三张表中

这三张表的建表语句

# Dump of table roles_permissions
# ------------------------------------------------------------

DROP TABLE IF EXISTS `roles_permissions`;

CREATE TABLE `roles_permissions` (
                                     `id` bigint(20) NOT NULL AUTO_INCREMENT,
                                     `role_name` varchar(100) DEFAULT NULL,
                                     `permission` varchar(100) DEFAULT NULL,
                                     PRIMARY KEY (`id`),
                                     UNIQUE KEY `idx_roles_permissions` (`role_name`,`permission`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

LOCK TABLES `roles_permissions` WRITE;
/*!40000 ALTER TABLE `roles_permissions` DISABLE KEYS */;

INSERT INTO `roles_permissions` (`id`, `role_name`, `permission`)
VALUES
(4,'admin','video:*'),
(3,'role1','video:buy'),
(2,'role1','video:find'),
(5,'role2','*'),
(1,'root','*');

/*!40000 ALTER TABLE `roles_permissions` ENABLE KEYS */;
UNLOCK TABLES;


# Dump of table user_roles
# ------------------------------------------------------------

DROP TABLE IF EXISTS `user_roles`;

CREATE TABLE `user_roles` (
                              `id` bigint(20) NOT NULL AUTO_INCREMENT,
                              `username` varchar(100) DEFAULT NULL,
                              `role_name` varchar(100) DEFAULT NULL,
                              PRIMARY KEY (`id`),
                              UNIQUE KEY `idx_user_roles` (`username`,`role_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

LOCK TABLES `user_roles` WRITE;
/*!40000 ALTER TABLE `user_roles` DISABLE KEYS */;

INSERT INTO `user_roles` (`id`, `username`, `role_name`)
VALUES
(1,'jack','role1'),
(2,'jack','role2'),
(4,'xdclass','admin'),
(3,'xdclass','root');

/*!40000 ALTER TABLE `user_roles` ENABLE KEYS */;
UNLOCK TABLES;


# Dump of table users
# ------------------------------------------------------------

DROP TABLE IF EXISTS `users`;

CREATE TABLE `users` (
                         `id` bigint(20) NOT NULL AUTO_INCREMENT,
                         `username` varchar(100) DEFAULT NULL,
                         `password` varchar(100) DEFAULT NULL,
                         `password_salt` varchar(100) DEFAULT NULL,
                         PRIMARY KEY (`id`),
                         UNIQUE KEY `idx_users_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

LOCK TABLES `users` WRITE;
/*!40000 ALTER TABLE `users` DISABLE KEYS */;

INSERT INTO `users` (`id`, `username`, `password`, `password_salt`)
VALUES
(1,'jack','123',NULL),
(2,'xdclass','456',NULL);

/*!40000 ALTER TABLE `users` ENABLE KEYS */;
UNLOCK TABLES;


  • 配置jdbcRealm.ini
# 声明Realm 指定realm类型
jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm

# 配置数据源
dataSource=com.alibaba.druid.pool.DruidDataSource
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql://localhost:3306/test_shiro??characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=Asia/Shanghai
dataSource.username=root
dataSource.password=123456

# 指定数据源
jdbcRealm.dataSource=$dataSource

# 开启查询权限
jdbcRealm.permssionLookupEnabled=true

# 指定SecurityManager的Realms实现,设置realms,如果有多个的话用逗号分隔
securityManager.realms=$jdbcRealm
  • 使用
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;

/**
 * @author sherry
 * @description
 * @date Create in 2020/4/7
 * @modified By:
 */

public class JdbcRealmTest {
    @Test
    public void testAuthentication(){
        //        创建SecurityManager工程,通过配置文件ini创建

        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:jdbcRealm.ini");
        SecurityManager securityManager = factory.getInstance();

//        将SecurityManager设置到当前运行环境中
        SecurityUtils.setSecurityManager(securityManager);

        Subject subject = SecurityUtils.getSubject();
        //        构造认证信息
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("admin", "123456");
//        登陆认证
        subject.login(usernamePasswordToken);

        System.out.println("认证结果:" + subject.isAuthenticated());//true

        System.out.println("当前用户是否有root角色:" + subject.hasRole("root"));
        System.out.println("当前用户是否有user角色:" + subject.hasRole("user"));


        System.out.println("当前用户是否有video:buy权限" + subject.isPermitted("video:buy"));


        System.out.println("subject别名:" + subject.getPrincipal());

//        不同于subject.hasRole("root"),校验失败的话会抛出异常
        try {
            subject.checkRole("root");
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
//        退出登录
        subject.logout();

    }
}

我们发现,代码上和使用IniRealm的时候完全一样。

我们可以这么理解,目前为止,在配置上,我们都使用的是ini文件,但是,当使用IniRealm的时候,直接将信息存储在了ini文件中,使用JdbcRealm的时候,ini文件只配置数据库,并且告诉Shiro去数据库中查找用户角色权限信息。


除此之外,我们也可以在代码中配置数据源,并且设置自定义sql

import com.alibaba.druid.pool.DruidDataSource;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.jdbc.JdbcRealm;
import org.apache.shiro.subject.Subject;
import org.junit.Test;

public class JdbcRealmTest {

	DruidDataSource dataSource = new DruidDataSource();
	
	{
		dataSource.setUrl("jdbc:mysql://localhost:3306/test");
		dataSource.setUsername("root");
		dataSource.setPassword("amoscxy123");
	}
	
	@Test
	public void testAuthentication(){
		//在不设置查询语句的时候,JdbcRealm会使用默认的查询语句
		JdbcRealm jdbcRealm = new JdbcRealm();
		jdbcRealm.setDataSource(dataSource);
		//设置权限的默认开关为true,
		jdbcRealm.setPermissionsLookupEnabled(true);
		
		//自定义的sql语句
		String sql = "select password from test_user where user_name = ?";
		jdbcRealm.setAuthenticationQuery(sql);
		
		String roleSql = "select role_name from test_user_role where user_name = ?";
		jdbcRealm.setUserRolesQuery(roleSql);
		
		//1.构建SecurityManager
		DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
		defaultSecurityManager.setRealm(jdbcRealm);
		
		//2.主体提交认证请求
		SecurityUtils.setSecurityManager(defaultSecurityManager);
		Subject subject = SecurityUtils.getSubject();
		
		UsernamePasswordToken token = new UsernamePasswordToken("xiaoming","654321");
		subject.login(token);
		
		System.out.println("isAuthenticated:"+subject.isAuthenticated());
		
		subject.checkRole("user");
		
//		subject.checkPermission("user:select");
		
	}
}

自定义Realm

package com.example.demo1.config;

import com.example.demo1.domain.Permission;
import com.example.demo1.domain.Role;
import com.example.demo1.domain.User;
import com.example.demo1.service.UserService;
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.springframework.beans.factory.annotation.Autowired;

import java.util.ArrayList;
import java.util.List;

/**
 * @author sherry
 * @description
 * @date Create in 2019/10/12
 * @modified By:
 */

public class CustomRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;


    /**
     * 重写授权逻辑:权限校验的时候调用
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("授权 doGetAuthorizationInfo");

//        除了CacheManager外,也可以在这里,业务层面上添加缓存

        User newUser = (User)principals.getPrimaryPrincipal();
        String username = newUser.getUsername();
        User user = userService.findAllUserInfoByUsername(username);


        List<String> stringRoleList = new ArrayList<>();
        List<String> stringPermissionList = new ArrayList<>();


        List<Role> roleList = user.getRoleList();

        for (Role role : roleList) {
            stringRoleList.add(role.getName());

            List<Permission> permissionList = role.getPermissionList();

            for (Permission p : permissionList) {
                if (p != null) {
                    stringPermissionList.add(p.getName());
                }
            }

        }

        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.addRoles(stringRoleList);
        simpleAuthorizationInfo.addStringPermissions(stringPermissionList);

        return simpleAuthorizationInfo;
    }

    /**
     * 重写认证逻辑:用户登录的时候调用
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("认证 doGetAuthenticationInfo");

        //从token获取用户信息,token代表用户输入
        String username = (String) token.getPrincipal();

        User user = userService.findAllUserInfoByUsername(username);

        //取密码
        String pwd = user.getPassword();
        if (pwd == null || "".equals(pwd)) {
            return null;
        }

        return new SimpleAuthenticationInfo(user, user.getPassword(), this.getClass().getName());
    }
}

案例整合

https://gitee.com/test-qqqq/learning-shiro/tree/master/demo1

发布了102 篇原创文章 · 获赞 12 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/m0_37208669/article/details/105392769