1、Shiro简介
1.1 什么是Shiro?
-
Shiro他是Apache下面的一个java的安全框架。
-
Shiro可以非常容易的开发出足够好的应用,其不仅可以用在javaSE环境,也可以用在JavaEE环境。
-
Shiro可以完成,认证,授权,加密,会话管理,Web集成,缓存等。
2.环境搭配
2.1创建一个Springboot项目
-
引入相关依赖jar包
<?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.3.4.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.ddf</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> <!--引入shiro Subject 用户 SecurityManager 管理所有用户 Reolm 连接数据 --> <!--引入shiro整合Spring的包--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.1</version> </dependency> <!--引入web--> <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> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
-
创建Controller类测试项目是否能够运行
1.新建index.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>
add.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>add</h1> </body> </html>
update.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>update</h1> </body> </html>
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> <hr> <p th:text="${msg}" style="color: red"></p> <form th:action="@{/login}"> <p>用户名:<input type="text" name="username" /></p> <p>密码:<input type="text" name="password" /></p> <p><input type="submit" name="登录" /></p> </form> </body> </html>
2.创建MyController类:
package com.ddf.controller; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.subject.Subject; 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","Hello Shiro"); return "index"; } @RequestMapping("/user/add") public String toAdd(){ return "user/add"; } @RequestMapping("/user/update") public String toUpdate(){ return "user/update"; } @RequestMapping("/toLogin") public String toLogin(){ return "login"; } @RequestMapping("/login") public String login(String username,String password,Model model){ //获取当前的用户 Subject subject = SecurityUtils.getSubject(); //封装用户的登录数据 UsernamePasswordToken token = new UsernamePasswordToken(username, password); try { //执行登录的方法,如果没有异常就说明ok了 subject.login(token); return "index"; }catch (UnknownAccountException e){ //用户名错误 model.addAttribute("msg","用户名错误"); return "login"; }catch (IncorrectCredentialsException e){ //密码不存在 model.addAttribute("msg","密码错误"); return "login"; } } //进入未授权页面 @RequestMapping("/noauth") @ResponseBody public String unauthorized(){ return "未授权无法访问此页面"; } }
2.2创建配置类
-
创建UserRealm类继承AuthorizingRealm获取授权和认证的权限
package com.ddf.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; 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类(把UserRealm注入到Bean中)
package com.ddf.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 { //第一步:创建realm对象,需要定义类 @Bean public UserRealm userRealm(){ return new UserRealm(); } //第二步:DefaultWebSecurityManager @Bean(name = "securityManager") public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){ DefaultWebSecurityManager securityManager= new DefaultWebSecurityManager(); //关联UserRealm securityManager.setRealm(userRealm); return securityManager; } //第三步:ShiroFileFactoryBean @Bean public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){ ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean(); //设置安全管理器 bean.setSecurityManager(defaultWebSecurityManager); //添加shiro的内置过滤器 /* * anon:无需认证就可以访问 * authc:必须认证了才能访问 * user:必须拥有 记住我 功能才能用 * perms:拥有对某个资源的权限才能访问 * role:拥有某个角色权限才能访问 * */ //登录拦截 Map<String,String> fileMap = new LinkedHashMap<>(); // fileMap.put("/user/add","authc"); // fileMap.put("/user/update","authc"); //请求多个认证可使用/* fileMap.put("/user/*","authoc"); bean.setFilterChainDefinitionMap(fileMap); //设置登录请求 bean.setLoginUrl("/toLogin"); return bean; } }
-
设置需要认证的用户,如果未认证返回login页面
//登录拦截 Map<String,String> fileMap = new LinkedHashMap<>(); // fileMap.put("/user/add","authc"); // fileMap.put("/user/update","authc"); //请求多个认证可使用/* fileMap.put("/user/*","authoc"); bean.setFilterChainDefinitionMap(fileMap); //设置登录请求 bean.setLoginUrl("/toLogin");
-
通过login页面获取登录用户信息,判断用户的账号和密码是否正确
@RequestMapping("/login") public String login(String username,String password,Model model){ //获取当前的用户 Subject subject = SecurityUtils.getSubject(); //封装用户的登录数据 UsernamePasswordToken token = new UsernamePasswordToken(username, password); try { //执行登录的方法,如果没有异常就说明ok了 subject.login(token); return "index"; }catch (UnknownAccountException e){ //用户名错误 model.addAttribute("msg","用户名错误"); return "login"; }catch (IncorrectCredentialsException e){ //密码不存在 model.addAttribute("msg","密码错误"); return "login"; }
-
通过自定义的UserRealm认证方法进行用户的一个身份认证
//认证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("执行了=》doGetAuthenticationInfo"); //设置用户名和密码 数据库中取 String name = "root"; String password = "123"; UsernamePasswordToken userToken = (UsernamePasswordToken)token; //判断登录的用户是否等于当前的用户 if(!userToken.getUsername().equals(name)){ return null; //抛出异常 UnknownAccountException } return new SimpleAuthenticationInfo("",password,""); }
3.使用Shiro整合Mybatis
-
引入相关的jar包
<!--Druid--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.21</version> </dependency> <!--log4j--> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <!--mysql--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency>
-
配置application.yml文件(配置druid数据源)
spring: datasource: username: root password: 123 #假如时区报错误了,就增加一个时区的配置就OK了, serverTimezone=UTC url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&userUnicode=true&characterEncoding=utf-8 driver-class-name: com.mysql.cj.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource #Spring Boot 默认是不注入这些属性值的,需要自己绑定 #druid 数据源专有配置 initialSize: 5 minIdle: 5 maxActive: 20 maxWait: 60000 timeBetWeenEvictionRunsMillis: 60000 minEvictableIdleTimeMillis: 30000 validationQuery: true testOnBorrow: false testOnReturn: false poolPreparedStatements: true #配置监控统计拦截的filters,stat:监控统计,Log4j:日志记录,wall:防御sql注入 #如果允许时报错 java.Lang.ClassNotFoundException: org.apache.Log4j.Priority #则导入 Log4j 依赖即可,Maven 地址:https://mvnrepository.com/ortifact/logyj/log4j filters: stat,wall,log4j maxPoolPreparedStatementPerConnectionSize: 20 userGlobelDataSourceStat: true connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
-
创建实体类和mapper接口
1.User实体类:
package com.ddf.pojo; public class User { private Integer id; private String name; private String pwd; public User(){} public User(Integer id, String name, String pwd) { this.id = id; this.name = name; this.pwd = pwd; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPwd() { return pwd; } public void setPwd(String pwd) { this.pwd = pwd; } }
2.UserMapper
package com.ddf.mapper; import com.ddf.pojo.User; import org.apache.ibatis.annotations.Mapper; import org.springframework.stereotype.Repository; @Repository @Mapper public interface UserMapper { public User queryUserByName(String name); }
3.UserService
package com.ddf.service; import com.ddf.pojo.User; import org.springframework.stereotype.Service; public interface UserService { public User queryUserByName(String name); }
4.UserServiceImpl
package com.ddf.service; import com.ddf.mapper.UserMapper; import com.ddf.pojo.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Override public User queryUserByName(String name) { return userMapper.queryUserByName(name); } }
5.在resource目录下配置mapper.xml文件
1. 创建UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.ddf.mapper.UserMapper"> <!--加入<cache/>表示开始缓存--> <select id="queryUserByName" parameterType="String" resultType="User"> select * from user where name=#{name} </select> </mapper>
2.配置application.properties文件
#配置mybatis的配置文件 mybatis.type-aliases-package=com.ddf.pojo mybatis.mapper-locations=classpath:mapper/*.xml
3.测试用户是否能被查到
package com.ddf.shirospringboot; import com.ddf.service.UserService; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class ShiroSpringbootApplicationTests { @Autowired UserService userService; @Test void contextLoads() { System.out.println(userService.queryUserByName("张三")); } }
4.使用数据库用户名替换自定义的用户名
-
UserRealm
package com.ddf.config; import com.ddf.pojo.User; import com.ddf.service.UserService; import org.apache.shiro.authc.*; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.springframework.beans.factory.annotation.Autowired; public class UserRealm extends AuthorizingRealm { @Autowired UserService userService; //授权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("执行了=》授权doGetAuthorizationInfo"); //给用户授权 SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); info.addStringPermission("user:add"); //拿到当前登录的这个对象 Subject subject = SecurityUtils.getSubject(); //拿到User对象 User currentUser = (User) subject.getPrincipal(); info.addStringPermission(currentUser.getPerms()); return info; } //认证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("执行了=》doGetAuthenticationInfo"); UsernamePasswordToken userToken = (UsernamePasswordToken)token; //自定义用户时可使用这个测试 // String name = "root"; // String password = "123"; //判断登录的用户是否等于当前的用户 /*if(!userToken.getUsername().equals(name)){ return null; //抛出异常 UnknownAccountException } return new SimpleAuthenticationInfo("",password,"");*/ //连接真实的数据库 User user = userService.queryUserByName(userToken.getUsername()); if(user==null){ //没有这个用户 return null; } //可以加密: MD5:e10adc3949ba59abbe56e057f20f883e //MD5盐值加密:e10adc3949ba59abbe56e057f20f883eusername //密码认证,shiro做,加密了 return new SimpleAuthenticationInfo("",user.getPwd(),""); } }
2.在ShiroConfig的getShiroFilterFactoryBean方法中添加授权,未授权的跳转至未授权页面
//第三步:ShiroFileFactoryBean @Bean public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){ ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean(); //设置安全管理器 bean.setSecurityManager(defaultWebSecurityManager); //添加shiro的内置过滤器 /* * anon:无需认证就可以访问 * authc:必须认证了才能访问 * user:必须拥有 记住我 功能才能用 * perms:拥有对某个资源的权限才能访问 * role:拥有某个角色权限才能访问 * */ //登录拦截 Map<String,String> fileMap = new LinkedHashMap<>(); // fileMap.put("/user/add","authc"); // fileMap.put("/user/update","authc"); //请求多个认证可使用/* //fileMap.put("/user/*","authoc"); //添加授权 //正常情况下没有授权就会调到未授权的页面去 fileMap.put("/user/add","perms[1]"); fileMap.put("/user/update","perms[2]"); bean.setFilterChainDefinitionMap(fileMap); //设置登录请求 bean.setLoginUrl("/toLogin"); //跳转至未授权页面 bean.setUnauthorizedUrl("/noauth"); return bean; }
5.shiro整合thymeleaf
-
引入相关的jar包
<!--引入shiro和thymeleaf的整合包--> <dependency> <groupId>com.github.theborakompanioni</groupId> <artifactId>thymeleaf-extras-shiro</artifactId> <version>2.0.0</version> </dependency>
-
在ShiroConfig中整合ShiroDialect,把它放入bean中,用来整合:shiro thymeleaf
//整合ShiroDialect:用来整合Shiro thymeleaf @Bean public ShiroDialect getShiroDialect(){ return new ShiroDialect(); }
-
用户有那个页面的权限就显示那个页面
1.引入shiro 的头文件
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro">
2.修改index首页使用th:hasPermission进行判断
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>首页</h1> <!--从session中判断值--> <div th:if="${session.loginUser==null}"> <a th:href="@{/toLogin}">登录</a> </div> <p th:text="${msg}"></p> <hr> <div shiro:hasPermission="1"> <a th:href="@{/user/add}">add</a> </div> <div shiro:hasPermission="2"> <a th:href="@{/user/update}">update</a> </div> </body> </html>
3.登录成功就传一个session对象,在认证中传一个user对象
//认证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("执行了=》doGetAuthenticationInfo"); UsernamePasswordToken userToken = (UsernamePasswordToken)token; //自定义时可使用这个测试 // String name = "root"; // String password = "123"; //判断登录的用户是否等于当前的用户 /*if(!userToken.getUsername().equals(name)){ return null; //抛出异常 UnknownAccountException } return new SimpleAuthenticationInfo("",password,"");*/ //连接真实的数据库 User user = userService.queryUserByName(userToken.getUsername()); if(user==null){ //没有这个用户 return null; } Subject currentSubject = SecurityUtils.getSubject(); Session session = currentSubject.getSession(); session.setAttribute("loginUser",user); //可以加密: MD5:e10adc3949ba59abbe56e057f20f883e //MD5盐值加密:e10adc3949ba59abbe56e057f20f883eusername //密码认证,shiro做,加密了 return new SimpleAuthenticationInfo(user,user.getPwd(),""); }
4.通过user对象就可以在前端页面进行一个判断,如果用户登录成功就不显示登录按钮
<!--从session中判断值--> <div th:if="${session.loginUser==null}"> <a th:href="@{/toLogin}">登录</a> </div>