Shiro-01-快速入门
Shiro
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。
使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
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
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- gym 搭建 RL 环境
- MNIST练习
- Dinosaurus_Island_Character_level_language_model_final_v3b
- Trigger_word_detection_v1a
- 《深入浅出SQL》问答录(二)
- 《深入浅出SQL》问答录(四)
- 《深入浅出MySQL》问答录(五)
- 《深入浅出SQL》问答录(七)
- 《深入浅出SQL》问答录(八)
- Improvise_a_Jazz_Solo_with_an_LSTM_Network_v3a-2
- 《深入浅出SQL》问答录(九)
- 《深入浅出SQL》问答录(十)
- Operations_on_word_vectors_v2a
- LeetCode精选好题(一)
- LeetCode精选好题(二)