Shiro-01-快速入门

时间:2021-09-20
本文章向大家介绍Shiro-01-快速入门,主要包括Shiro-01-快速入门使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

Shiro

Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。

使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。

官网:https://shiro.apache.org


1 快速入门

1.1 github 项目

首先,找到 Shiro 托管在 Github 上的源码:https://github.com/apache/shiro

然后打开里面的 samples/quickstart 文件夹,查看 pom.xml 依赖并且根据它的依赖来配置我们自己的依赖

<dependencies>
        <!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-core -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.8.0</version>
        </dependency>

        <!-- configure logging -->
        <!-- https://mvnrepository.com/artifact/org.slf4j/jcl-over-slf4j -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <version>1.7.25</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-log4j12 -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.25</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/log4j/log4j -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
</dependencies>

然后可以看到示例项目的文件夹下的两个配置文件,一个是 log4j 的,一个是 shiro 的:

复制这两个文件的内容到自己的项目下,然后将 QuickStart 类复制到自己的项目下:

其中可能要修改部分 import 的代码,主要是:

import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.util.Factory;

这两个 import 语句。注意将 pom 文件中的 scope 设置成 runtime 或者干脆去掉,运行主程序:

2021-09-19 22:08:32,696 INFO [org.apache.shiro.session.mgt.AbstractValidatingSessionManager] - Enabling session validation scheduler... 
2021-09-19 22:08:33,057 INFO [Quickstart] - Retrieved the correct value! [aValue] 
2021-09-19 22:08:33,058 INFO [Quickstart] - User [lonestarr] logged in successfully. 
2021-09-19 22:08:33,059 INFO [Quickstart] - May the Schwartz be with you! 
2021-09-19 22:08:33,059 INFO [Quickstart] - You may use a lightsaber ring.  Use it wisely. 
2021-09-19 22:08:33,059 INFO [Quickstart] - You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'.  Here are the keys - have fun! 

1.2 QuickStart 解读

我们慢慢地看这些代码:

具体代码:

public static void main(String[] args) {

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

		// 获取当前用户对象 subject
		Subject currentUser = SecurityUtils.getSubject();

		// 通过当前用户拿到 session
		Session session = currentUser.getSession();
		session.setAttribute("someKey", "你好,世界!");
		String value = (String) session.getAttribute("someKey");
		if (value.equals("你好,世界!")) {
			log.info("得到了正确的结果! [" + value + "]");
		}


		// 判断当前的用户是否被认证
		if (!currentUser.isAuthenticated()) {
			// new 一个 token,通过用户名和密码
			UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
			token.setRememberMe(true);	// 设置 记住我
			try {
				currentUser.login(token);	// 执行登录操作
			} catch (UnknownAccountException uae) {	// 未知账户
				log.info("There is no user with username of " + token.getPrincipal());
			} catch (IncorrectCredentialsException ice) {
				log.info("Password for account " + token.getPrincipal() + " was incorrect!");
			} catch (LockedAccountException lae) {	// 用户被锁定
				log.info("The account for username " + token.getPrincipal() + " is locked.  " +
						"Please contact your administrator to unlock it.");
			}
			// ... catch more exceptions here (maybe custom ones specific to your application?
			catch (AuthenticationException ae) {	// 认证异常
				//unexpected condition?  error?
			}
		}

		// 表明是谁:
		//print their identifying principal (in this case, a username):
		log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");

		// 测试角色
		if (currentUser.hasRole("schwartz")) {
			log.info("你拥有 schwartz 的角色");
		} else {
			log.info("你只是凡人.");
		}

		// 测试权限(粗粒度)
		if (currentUser.isPermitted("lightsaber:wield")) {
			log.info("你拥有 lightsaber:* 的权限");
		} else {
			log.info("抱歉,你没有 lightsaber:* 的权限");
		}

		// 测试权限(细粒度),带有参数
		if (currentUser.isPermitted("winnebago:drive:eagle5")) {
			log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'.  " +
					"Here are the keys - have fun!");
		} else {
			log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
		}

		// 注销
		currentUser.logout();

		System.exit(0);
	}

比较核心的几个方法或者步骤就有:

Subject currentUser = SecurityUtils.getSubject();
Session session = currentUser.getSession();
currentUser.isAuthenticated()
currentUser.getPrincipal()
currentUser.hasRole("schwartz")
currentUser.isPermitted("lightsaber:wield")
currentUser.logout()

1.3 SpringBoot 整合 Shiro

我的 github 配套源码:https://github.com/Amor128/shiro/tree/master/my-shiro/my-shiro-02-ShiroInSpringBoot

三大对象:

  • Subject 用户
  • SecurityManager 管理所有用户
  • Realm 连接数据

1.3.1 环境搭建

先看 pom 依赖,主要就是 SpringBoot 整合 Shiro 的依赖:

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.8.0</version>
</dependency>

再看项目目录:

对于 UserRealm 文件:

package com.ermao.config;

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;

/**
 * 自定义的 Realm,继承自 AuthorizingRealm
 * @author Ermao
 * Date: 2021/9/19 23:19
 */
public class UserRealm extends AuthorizingRealm {
	// 授权
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
		System.out.println("执行了授权==>doGetAuthorizationInfo");
		return null;
	}

	// 认证
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
		System.out.println("执行了认证==>doGetAuthenticationInfo");
		return null;
	}
}

ShiroConfig 文件:

package com.ermao.config;

import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @author Ermao
 * Date: 2021/9/19 23:16
 */
@Configuration
public class ShiroConfig {
	// 1. 创建 ShiroFilterFactoryBean
	@Bean
	public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager getDefaultWebSecurityManager) {
		ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
		// 设置安全管理器
		shiroFilterFactoryBean.setSecurityManager(getDefaultWebSecurityManager);
		
		// 添加 shiro 内置过滤器
		// anon 无需认证
		// authc 必须认证才能访问
		// user 类必须拥有 记住我 才能使用
		// perms 拥有对某个资源的权限才能访问
		// role 拥有某个角色产能访问
		Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
//		filterChainDefinitionMap.put("/user/insert", "anon");
//		filterChainDefinitionMap.put("/user/update", "authc");
		filterChainDefinitionMap.put("/user/*", "authc");
		shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
		shiroFilterFactoryBean.setLoginUrl("/toLogin");	// 设置登录请求

		return shiroFilterFactoryBean;
	}

	// 2. 创建 DefaultWebSecurityManager
	@Bean
	public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
		DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
		// 管理 realm
		defaultWebSecurityManager.setRealm(userRealm);
		return defaultWebSecurityManager;
	}

	// 1. 创建 Realm 对象,自定义类
	@Bean
	public UserRealm userRealm() {
		return new UserRealm();
	}
}

Shiro 的配置就是这样固定的套路,搭建好之后自己改就完事了。


1.3.2 拦截

主要是在这段代码中:

// 添加 shiro 内置过滤器
// anon 无需认证
// authc 必须认证才能访问
// user 类必须拥有 记住我 才能使用
// perms 拥有对某个资源的权限才能访问
// role 拥有某个角色产能访问
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
//		filterChainDefinitionMap.put("/user/insert", "anon");
//		filterChainDefinitionMap.put("/user/update", "authc");
filterChainDefinitionMap.put("/user/*", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
shiroFilterFactoryBean.setLoginUrl("/toLogin");	// 设置登录请求

1.3.3 用户认证

将我们的 UserRealm 文件修改成这样:

// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    System.out.println("执行了认证==>doGetAuthenticationInfo");

    // 封装用户当前登录数据
    // 用户名,密码 从数据库中取
    String username = "admin";
    String password = "12345";
    UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;

    if (!token.getUsername().equals(username)) {
        return null;	// 抛出异常 UnknownAccountException
    }

    // 密码认证由 shiro 框架完成
    // 还可以进行加密 MD5、MD5盐值加密
    return new SimpleAuthenticationInfo("", password, "");
}

controller 中的代码是这样的:

	@RequestMapping("/login")
	public String login(String username, String password, Model model) {
		Subject subject = SecurityUtils.getSubject();	// 获取 shiro subject 对象
		UsernamePasswordToken token = new UsernamePasswordToken(username, password);	// 构造密码 token
		try {
			subject.login(token);	// 执行登录的方法,如果没有异常就说明 OK
			return "index";
		} catch (UnknownAccountException e) {	// 用户名不存在
			model.addAttribute("msg", "用户名错误");
			return "login";
		} catch (IncorrectCredentialsException e) {	// 用户名不存在
			model.addAttribute("msg", "密码错误");
			return "login";
		}
	}

最后的效果是这样的:

1.3.4 用户授权

鉴权代码:

filterChainDefinitionMap.put("/user/insert", "perms[user:insert]");

注意要把这段代码放在拦截代码的前面

用户在认证的时候就应该拿到用户的权限(从数据库中),以下是认证部分的代码:

	// 认证
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
		System.out.println("执行了认证==>doGetAuthenticationInfo");

		// 封装用户当前登录数据
		// 用户名,密码 从数据库中取
		UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
		User user = userService.selectUserByUsername(token.getUsername());
		if (user == null) {
			return null;	// 抛出异常 UnknownAccountException
		}
		// 密码认证由 shiro 框架完成
		// 还可以进行加密 MD5、MD5盐值加密
		return new SimpleAuthenticationInfo(user, user.getPwd(), "");
	}

以下是授权部分的代码:

	// 授权
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
		System.out.println("执行了授权==>doGetAuthorizationInfo");

		Subject subject = SecurityUtils.getSubject();
		User currentUser = (User) subject.getPrincipal();

		SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
		info.addStringPermission(currentUser.getPermissions());
		return info;
	}

概括图,注意其中取得用户并且作为 principle 传递到授权的过程:

经过上述配置就可以实现用户授权并且基于权限拦截了。


1.3.5 整合 Thymeleaf 和 Shiro

整合好这两个组件之后就可以使用 Shiro 的 Dialect 来实现 view 组件的显示和隐藏,直接上代码:

首页:

<!DOCTYPE html>
<html lang="en"
      xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"
      xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>首页</h1>
<p th:text="${msg}"></p>

<div shiro:notAuthenticated="">
    <a th:href="@{/toLogin}">登录</a>
</div>

<div shiro:hasPermission="user:insert"><a th:href="@{user/insert}">insert</a>
</div>
<div shiro:hasPermission="user:update"><a th:href="@{user/update}">update</a>
</div>
<div shiro:authenticated="">
    <form th:action="@{/logout}">
        <input type="submit" value="注销">
    </form>
</div>

</body>
</html>

其他页面类似,不做赘述,主要就是使用 shiro:打头的标签属性

原文地址:https://www.cnblogs.com/locustree/p/15314210.html