Shrio
Shrio快速入门
Subject用户
SecurityManager 管理所有用户
Realm 连接数据
-
导入依赖
<?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"> <parent> <artifactId>spring-08-shiro</artifactId> <groupId>com.wen</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>hello-shiro</artifactId> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.4.1</version> </dependency> <!-- configure logging --> <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> <scope>runtime</scope> <version>1.7.21</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <scope>runtime</scope> <version>1.7.21</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <scope>runtime</scope> <version>1.2.17</version> </dependency> </dependencies> </project>
-
编写配置文件
[users] root = secret, admin guest = guest, guest presidentskroob = 12345, president darkhelmet = ludicrousspeed, darklord, schwartz lonestarr = vespa, goodguy, schwartz [roles] # 'admin' role has all permissions, indicated by the wildcard '*' admin = * # The 'schwartz' role can do anything (*) with any lightsaber: schwartz = lightsaber:* # The 'goodguy' role is allowed to 'drive' (action) the winnebago (type) with # license plate 'eagle5' (instance specific id) goodguy = winnebago:drive:eagle5
-
测试
/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.*; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.session.Session; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Simple Quickstart application showing how to use Shiro's API. * * @since 0.9 RC2 */ public class Quickstart { private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class); public static void main(String[] args) { // The easiest way to create a Shiro SecurityManager with configured // realms, users, roles and permissions is to use the simple INI config. // We'll do that by using a factory that can ingest a .ini file and // return a SecurityManager instance: // Use the shiro.ini file at the root of the classpath // (file: and url: prefixes load from files and urls respectively): Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini"); SecurityManager securityManager = factory.getInstance(); // for this simple example quickstart, make the SecurityManager // accessible as a JVM singleton. Most applications wouldn't do this // and instead rely on their container configuration or web.xml for // webapps. That is outside the scope of this simple quickstart, so // we'll just do the bare minimum so you can continue to get a feel // for things. SecurityUtils.setSecurityManager(securityManager); // Now that a simple Shiro environment is set up, let's see what you can do: // get the currently executing user: //获取当前的用户的对象Subject Subject currentUser = SecurityUtils.getSubject(); // Do some stuff with a Session (no need for a web or EJB container!!!) //通过当前用户拿到Session Session session = currentUser.getSession(); session.setAttribute("someKey", "aValue"); String value = (String) session.getAttribute("someKey"); if (value.equals("aValue")) { log.info("Subject=>session [" + value + "]"); } //判断当前的用户是否被认证 if (!currentUser.isAuthenticated()) { //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? } } //say who they are: //print their identifying principal (in this case, a username): log.info("User [" + currentUser.getPrincipal() + "] logged in successfully."); //test a role: if (currentUser.hasRole("schwartz")) { log.info("May the Schwartz be with you!"); } else { log.info("Hello, mere mortal."); } //test a typed permission (not instance-level) //粗粒度 if (currentUser.isPermitted("lightsaber:wield")) { log.info("You may use a lightsaber ring. Use it wisely."); } else { log.info("Sorry, lightsaber rings are for schwartz masters only."); } //a (very powerful) Instance Level permission: //细粒度 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!"); } //all done - log out! //注销 currentUser.logout(); //结束 System.exit(0); } } ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210323210300230.png)
-
关键方法
//获取当前的用户的对象Subject
Subject currentUser = SecurityUtils.getSubject();
//通过当前用户拿到Session
Session session = currentUser.getSession();
//判断当前的用户是否被认证
currentUser.isAuthenticated()
//获得用户认证
currentUser.getPrincipal()
//获得用户角色
currentUser.hasRole
粗粒度
currentUser.isPermitted("lightsaber:wield")
//注销
currentUser.logout();
Shiro里有的 Spring Security里都有只是名字发生了改变
SpringBoot集成Shiro
编写测试环境
-
导入依赖
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.4.4</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.wen</groupId> <artifactId>shiro-springboot</artifactId> <version>0.0.1-SNAPSHOT</version> <name>shiro-springboot</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--thymeleaf--> <dependency> <groupId>org.thymeleaf</groupId> <artifactId>thymeleaf-spring5</artifactId> </dependency> <dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-java8time</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
-
编写
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>首页</h1> </body> </html>
-
编写Controller
package com.wen.shirospringboot.controller; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class MyController { @RequestMapping({"/","/index"}) public String toIndex(Model model){ model.addAttribute("msg","测试环境搭建"); return "index"; } }
-
测试环境是否搭建成功
前端正常显示参数,环境搭建成功
整合Shiro
-
导入依赖
<!--shiro整合spring的包--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.1</version> </dependency>
-
编写自定义UserRealm
package com.wen.shirospringboot.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; /** * @author 20212 * 自定义的 UserRealm */ 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("执行了=>认证doGetAuthorizationInfo"); return null; } }
-
编写ShiroConfig
package com.wen.shirospringboot.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; @Configuration public class ShiroConfig { //ShiroFilterFactoryBean public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){ ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean(); //设置安全管理器 bean.setSecurityManager(defaultWebSecurityManager); return bean; } //DefaultWebSecurityManager @Bean("securityManager") public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(userRealm); return securityManager; } //创建 realm 对象,需要自定义类1 @Bean public UserRealm userRealm() { return new UserRealm(); } }
-
编辑前端测试页面
user/add.html
和user/update.html
add页面<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>add</h1> </body> </html>
update页面
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>update</h1> </body> </html>
-
在
MyController
类下增加这两个方法用来跳转视图@RequestMapping("/user/add") public String add(){ return "user/add"; } @RequestMapping("/user/update") public String update(){ return "user/update"; }
-
测试能否正常访问
user/add
和user/update
Shiro实现登录拦截
Shrio里实现登录拦截功能是使用过滤器来完成的
-
添加Shiro内置的过滤器
- anon:无需认证就可以访问
- authc:必须认证了才能访问
- user:必须拥有
记住我
功能才可以访问 - perms:拥有某个资源的权限才能访问
- role:拥有某个角色权限才能访问
-
编写
login.html
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>首页</h1> <p th:text="${msg}"></p> <hr> <a th:href="@{/user/add}">add</a>|<a th:href="@{/user/update}">update</a> </body> </html>
-
在
MyController
类下增加toLogin
方法@RequestMapping("/toLogin") public String toLogin(){ return "login"; }
-
在
shiroConfig
类下的getShiroFilterFactoryBean
方法里设置如下//添加shiro的内置过滤器 /* anon:无需认证就能访问 authc:必须认证了才能访问 user:必须拥有 记住我 功能才能访问 perms:拥有对某个资源的权限才能访问 role:拥有某个角色权限才能访问 */ Map<String, String> map = new LinkedHashMap<>(); map.put("/user/*", "authc"); bean.setFilterChainDefinitionMap(map); //设置登录页面 bean.setLoginUrl("toLogin"); return bean;
-
测试能否成功拦截
访问/user/add
成功拦截并通过toLoginConttiroller
成功跳转到login.html
页面
Shiro实现拦截成功
Shiro实现用户认证
-
在
myController
类中编辑login
方法@RequestMapping("/login") public String login(String username, String password, Model model) { //获取当前用户 Subject subject = SecurityUtils.getSubject(); //封装用户的登录数据 UsernamePasswordToken token = new UsernamePasswordToken(username, password); try { subject.login(token); return "index"; } catch (UnknownAccountException e) {//用户名不存在 model.addAttribute("msg", "用户名不存在"); return "login"; } catch (IncorrectCredentialsException e) { model.addAttribute("msg", "密码错误"); return "login"; } }
-
在认证方法里编写
//认证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("执行了=>认证doGetAuthorizationInfo"); //伪造用户名,密码; String name="root"; String password="123456"; UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken; if(!userToken.getUsername().equals(name)){ //自动抛出异常 UnknownAccountException return null; } //密码认证,shiro帮我们做 return new SimpleAuthenticationInfo("",password,""); }
-
在
login.html
里显示用户登录失败信息,和表单请求跳转地址<form action="/login"> <p th:text="${msg}" style="color: red"></p> <p><input type="text" name="username">用户名</p> <p><input type="password" name="password">密码</p> <input type="submit" value="登录"> </form>
-
测试
密码错误正常显示
用户名不存在正常显示
用户名和密码都正确正常登录到首页
Shiro整合Mybatis
-
创建数据库
CREATE TABLE `user` ( `id` int(11) NOT NULL, `name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL, `pwd` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL, `perms` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; INSERT INTO `user` VALUES (1, 'wen', '123', 'user:add'); INSERT INTO `user` VALUES (3, '李四', '123456', NULL); INSERT INTO `user` VALUES (6, '李五', '12356', NULL); INSERT INTO `user` VALUES (7, 'root', '123456', 'user:update');
-
导入依赖
<!--lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.16.18</version> </dependency> <!--Druid--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.1</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.4</version> </dependency>
-
编写
application.yaml
文件spring: datasource: username: xxxx password: xxxx url: jdbc:mysql://cdb-1tjyxzq8.cd.tencentcdb.com:10198/mybatis?useUnicode=true&characterEncoding=utf-8 driver-class-name: com.mysql.cj.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource #druid数据源专有配置 initialSize: 5 minIdle: 5 maxActive: 20 maxWait: 60000 timeBetweenEvictionRunsMillis: 60000 minEvictableIdleTimeMillis: 300000 validationQuery: SELECT 1 FROM DUAL testWhileIdle: true testOnBorrow: false testOnReturn: false poolPreparedStatements: true #配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入 #如果允许报错,java.lang.ClassNotFoundException: org.apache.Log4j.Properity #则导入log4j 依赖就行 filters: stat,wall,log4j maxPoolPreparedStatementPerConnectionSize: 20 useGlobalDataSourceStat: true connectionoProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500 mybatis: type-aliases-package: com.wen.pojo mapper-locations: classpath:mapper/*.xml
-
编写UserMapper接口和对应的mapper文件
@Mapper @Repository public interface UserMapper { User queryUserByName(String name); }
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.wen.shirospringboot.mapper.UserMapper"> <select id="queryUserByName" resultType="com.wen.shirospringboot.pojo.User"> select * from user where name =#{name} </select> </mapper>
-
编写service接口和对应的实现
public interface UserService { User queryUserByName(String name); }
@Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Override public User queryUserByName(String name) { return userMapper.queryUserByName(name); } }
-
编写测试类 测试是否连接数据库成功
@SpringBootTest class ShiroSpringbootApplicationTests { @Autowired private UserService userService; @Test void contextLoads() { System.out.println(userService.queryUserByName("李四")); } }
正常返回结果连接数据库成功
-
把之前我么仿造的数据换成数据库里真是存在的数据
@Autowired private UserService userService; //认证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("执行了=>认证doGetAuthorizationInfo"); //伪造用户名,密码; UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken; User user = userService.queryUserByName(userToken.getUsername()); System.out.println("=>"+userToken.getUsername()); if (user==null){//没有这人 return null; } //密码认证,shiro帮我们做 return new SimpleAuthenticationInfo("",user.getPwd(),""); }
-
测试
能实现登录访问正常访问
Shiro请求授权实现
-
通过
SimpleAuthenticationInfo
传递当前用户的信息//认证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("执行了=>认证doGetAuthorizationInfo"); //伪造用户名,密码; UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken; User user = userService.queryUserByName(userToken.getUsername()); System.out.println("=>"+userToken.getUsername()); if (user==null){//没有这人 return null; } //密码认证,shiro帮我们做 return new SimpleAuthenticationInfo(user,user.getPwd(),""); }
-
添加授权信息
//授权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("执行了=>授权doGetAuthorizationInfo"); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); //拿到当前的登录对象 Subject subject = SecurityUtils.getSubject(); User principal = (User) subject.getPrincipal(); info.addStringPermission(principal.getPerms()); return info; }
-
添加权限访问
@Bean public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager) { ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean(); //设置安全管理器 bean.setSecurityManager(defaultWebSecurityManager); //添加shiro的内置过滤器 /* anon:无需认证就能访问 authc:必须认证了才能访问 user:必须拥有 记住我 功能才能访问 perms:拥有对某个资源的权限才能访问 role:拥有某个角色权限才能访问 */ Map<String, String> filterMap = new LinkedHashMap<>(); filterMap.put("/user/add","perms[user:add]"); filterMap.put("/user/update","perms[user:update]"); bean.setFilterChainDefinitionMap(filterMap); //设置登录页面 bean.setLoginUrl("/toLogin"); //设置没有权限 bean.setUnauthorizedUrl("/noauth"); return bean; }
-
测试
请求授权实现成功
Shiro整合Thymeleaf
现在我们的主页面是这个样子的
导 入依赖
-
导入依赖
<!--shiro整合thymeleaf的包--> <dependency> <groupId>com.github.theborakompanioni</groupId> <artifactId>thymeleaf-extras-shiro</artifactId> <version>2.0.0</version> </dependency>
-
在主页中进行设
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>首页</h1> <div shiro:guest="true"> <a th:href="@{/toLogin}">登录</a> </div> <div shiro:hasPermission="user:add"> <p th:text="${msg}"></p> </div> <hr> <div shiro:hasPermission="user:add"> <a th:href="@{/user/add}">add</a>|</div> <div shiro:hasPermission="user:update"> <a th:href="@{/user/update}">update</a> </div> </body> </html>
-
配置一个ben用来整合 shiro thymeleaf
//整合ShiroDialect:用来真个和 shiro thymeleaf @Bean public ShiroDialect getShiroDialect(){ return new ShiroDialect(); }
之后我们在登陆就能根据角色拥有的权限来显示页面了