shiro是apache 的一个强大且易用的java安全框架,执行身份验证、授权、密码学和会话管理。shiro 主要分为两个部分就是认证和授权两部分
一、介绍
-
subject代表了当前用户的安全操作
-
securitymanager:它是shiro框架的核心,典型的facade模式,shiro通过securitymanager来管理内部组件实例,并通过它来提供安全管理的各种服务。
-
authenticator即认证器,对用户身份进行认证,authenticator是一个接口,shiro提供modularrealmauthenticator实现类,通过modularrealmauthenticator基本上可以满足大多数需求,也可以自定义认证器。
-
authorizer即授权器,用户通过认证器认证通过,在访问功能时需要通过授权器判断用户是否有此功能的操作权限。
-
realm充当了shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,shiro会从应用配置的realm中查找用户及其权限信息。
-
sessionmanager即会话管理,shiro框架定义了一套会话管理,它不依赖web容器的session,所以shiro可以使用在非web应用上。
shiro相关类介绍
-
(1)authentication 认证 —— 用户登录
-
(2)authorization 授权 —- 用户具有哪些权限
-
(3)cryptography 安全数据加密
-
(4)session management 会话管理
-
(5)web integration web系统集成
-
(6)interations 集成其它应用,spring、缓存框架
二、依赖引入
完整的pom文件如下:
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.4.1
com.gt.shiro
com.sunyue.shiro
1.0-snapshot
jar
1.8
1.1.10
1.2.10
2.1.4
2.0.4
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-tomcat
org.springframework.boot
spring-boot-starter-jetty
org.mybatis.spring.boot
mybatis-spring-boot-starter
${mybatis.version}
org.springframework.boot
spring-boot-starter-thymeleaf
com.github.theborakompanioni
thymeleaf-extras-shiro
2.0.0
org.apache.shiro
shiro-spring
1.4.0
com.alibaba
druid-spring-boot-starter
${druid.verzion}
com.github.pagehelper
pagehelper-spring-boot-starter
${pagehelper.version}
mysql
mysql-connector-java
runtime
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-maven-plugin
ainclass>com.gt.shiro.springshiroapplication
三、配置文件
application.yml配置文件:
# 开发时关闭缓存,不然没法看到实时页面
spring.thymeleaf.cache=false
# 用非严格的 html
spring.thymeleaf.mode=html
spring.thymeleaf.encoding=utf-8
spring.thymeleaf.servlet.content-type=text/html
spring.datasource.druid.url=jdbc:mysql://localhost:3306/shiro?useunicode=true&characterencoding=utf-8&usessl=false&servertimezone=utc
spring.datasource.druid.username=root
spring.datasource.druid.password=admin
spring.datasource.druid.initial-size=1
spring.datasource.druid.min-idle=1
spring.datasource.druid.max-active=20
spring.datasource.druid.test-on-borrow=true
#springbootjdbc导入包不和以前一样
spring.datasource.druid.driver-class-name= com.mysql.cj.jdbc.driver
mybatis.type-aliases-package=com.gt.shiro.entity
mybatis.mapper-locations=classpath:mapper/*.xml
#打印数据库的操作
logging.level.com.example.springsecurity.dao=debug
#redis缓存
### 配置redis
mybatis.configuration.cache-enabled=true
# redis数据库索引(默认为0)
spring.redis.database=0
# redis地址
spring.redis.host=...
# redis服务器连接端口
spring.redis.port=6379
# redis服务器连接密码(默认为空)
spring.redis.password=sunyue
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-idle=200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.jedis.pool.max-wait=-1
# 连接池中的最小空闲连接
spring.redis.jedis.pool.min-idle=0
# 连接超时时间(毫秒)
spring.redis.timeout=1000
shiro两个重要的配置类:
-
1.userrealm
package com.gt.shiro.config; import com.gt.shiro.entity.testuser; import com.gt.shiro.server.testuserserver; import org.apache.shiro.securityutils; import org.apache.shiro.authc.*; 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.apache.shiro.subject.subject; import org.springframework.beans.factory.annotation.autowired; import java.util.arraylist; import java.util.hashset; import java.util.list; import java.util.set; public class userrealm extends authorizingrealm { @autowired private testuserserver testuserserver; /** * 执行授权逻辑 * * @param principalcollection * @return */ @override protected authorizationinfo dogetauthorizationinfo(principalcollection principalcollection) { system.out.println("执行授权逻辑"); /*获取当前登录的用户信息*/ subject subject = securityutils.getsubject(); testuser testuser = (testuser) subject.getprincipal(); //设置角色,多个角色 /*set
rolesset = new hashset<>(); rolesset.add(testuser.getrole());*/ //simpleauthorizationinfo info = new simpleauthorizationinfo(rolesset); //给资源进行授权 simpleauthorizationinfo info = new simpleauthorizationinfo(); /*可以在以下list加入多个权限*/ /*list roles = new arraylist<>(); roles.add(testuser.getperms()); info.addroles(roles);*/ //设置权限 info.addrole(testuser.getrole()); //需要判断权限是否为空值(null是没有地址,""是有地址但是里面的内容是空的) if (testuser.getperms() != null && !testuser.getperms().equals("")) { info.addstringpermission(testuser.getperms()); } return info; } /** * 执行认证逻辑 * * @param authenticationtoken * @return * @throws authenticationexception */ @override protected authenticationinfo dogetauthenticationinfo(authenticationtoken authenticationtoken) throws authenticationexception { system.out.println("执行认证逻辑"); /*获取令牌*/ usernamepasswordtoken passwordtoken = (usernamepasswordtoken) authenticationtoken; //取出用户名并且判断用户名是否和数据库一致 testuser testuser = testuserserver.selectonebyname(passwordtoken.getusername()); if (testuser != null) { //进行认证,将正确数据给shiro处理 //密码不用自己比对,authenticationinfo认证信息对象,一个接口,new他的实现类对象simpleauthenticationinfo /* 第一个参数随便放,可以放user对象,程序可在任意位置获取 放入的对象 * 第二个参数必须放密码, * 第三个参数放 当前realm的名字,因为可能有多个realm*/ //若密码不正确则返回incorrectcredentialsexception异常 return new simpleauthenticationinfo(testuser, testuser.getpassword(), this.getname()); } //若用户名不存在则返回unknownaccountexception异常 return null; } } -
2.shiroconfig
package com.gt.shiro.config; import at.pollux.thymeleaf.shiro.dialect.shirodialect; import org.apache.shiro.spring.security.interceptor.authorizationattributesourceadvisor; import org.apache.shiro.spring.web.shirofilterfactorybean; import org.apache.shiro.mgt.securitymanager; import org.apache.shiro.web.mgt.defaultwebsecuritymanager; import org.springframework.aop.framework.autoproxy.defaultadvisorautoproxycreator; import org.springframework.beans.factory.annotation.qualifier; import org.springframework.context.annotation.bean; import org.springframework.context.annotation.configuration; import org.springframework.web.servlet.handler.simplemappingexceptionresolver; import java.util.linkedhashmap; import java.util.map; import java.util.properties; @configuration public class shiroconfig { @bean public shirofilterfactorybean getshirofilterfactorybean(@qualifier("securitymanager") defaultwebsecuritymanager defaultwebsecuritymanager) { shirofilterfactorybean shirofilterfactorybean = new shirofilterfactorybean(); //设置安全管理器 shirofilterfactorybean.setsecuritymanager(defaultwebsecuritymanager); //添加一些shiro的内置过滤器 /** * shiro 的内置过滤器可以实现权限的相关拦截 * 常用过滤器 * 1.anon:无需认证 * 2.authc:必须认证才能访问 * 3.user:如果使用rememberme功能可以访问 * 4.perms:对应权限才能访问 * 5.role:对应角色才能访问 */ //登录状态下才可以访问main页面,manage权限可访问manage页面,admin角色可访问admin页面 map
filtermap = new linkedhashmap (); filtermap.put("/main", "authc"); filtermap.put("/manage", "perms[manage]"); filtermap.put("/admin", "roles[admin]"); shirofilterfactorybean.setfilterchaindefinitionmap(filtermap); //未登录状态下访问将跳转至login页面 // 如果不设置默认会自动寻找web工程根目录下的"/login.jsp"页面 shirofilterfactorybean.setlogin; // 登录成功后要跳转的链接 shirofilterfactorybean.setsuccess; //无授限状态下访问将请求unauthor shirofilterfactorybean.setunauthorized; return shirofilterfactorybean; } @bean(name = "securitymanager") public defaultwebsecuritymanager getdefaultwebsecuritymanager(@qualifier("userrealm") userrealm userrealm) { defaultwebsecuritymanager defaultwebsecuritymanager = new defaultwebsecuritymanager(); //defaultwebsecuritymanager需要关联一个realm defaultwebsecuritymanager.setrealm(userrealm); return defaultwebsecuritymanager; } /** * 创建realm */ @bean(name = "userrealm") public userrealm getrealm() { return new userrealm(); } @bean public shirodialect shirodialect() { return new shirodialect(); } /** * 开启shiro的注解(如@requiresroles,@requirespermissions) * 配置以下两个bean(defaultadvisorautoproxycreator和authorizationattributesourceadvisor)即可实现此功能 * * @return */ @bean public defaultadvisorautoproxycreator advisorautoproxycreator() { defaultadvisorautoproxycreator advisorautoproxycreator = new defaultadvisorautoproxycreator(); advisorautoproxycreator.setproxytargetclass(true); return advisorautoproxycreator; } /** * 开启 shiro 的@requirespermissions注解 * * @param securitymanager * @return */ @bean public authorizationattributesourceadvisor authorizationattributesourceadvisor(securitymanager securitymanager) { authorizationattributesourceadvisor authorizationattributesourceadvisor = new authorizationattributesourceadvisor(); authorizationattributesourceadvisor.setsecuritymanager(securitymanager); return authorizationattributesourceadvisor; } /** * shiro出现权限异常可通过此异常实现制定页面的跳转(或接口跳转) * * @return */ @bean public simplemappingexceptionresolver simplemappingexceptionresolver() { simplemappingexceptionresolver resolver = new simplemappingexceptionresolver(); properties properties = new properties(); /*未授权处理页*/ properties.setproperty("org.apache.shiro.authz.unauthorizedexception", "/error.html"); /*身份没有验证*/ properties.setproperty("org.apache.shiro.authz.unauthenticatedexception", "/error.html"); resolver.setexceptionmappings(properties); return resolver; } }
四、数据连接和业务逻辑
-
1.实体类
package com.gt.shiro.entity; import lombok.data; import lombok.experimental.accessors; import java.io.serializable; import java.util.date; @data @accessors(chain = true) public class testuser implements serializable { private integer id; private string username; private string password; /*权限*/ private string perms; /*角色*/ private string role; /*加盐密码*/ private string salt; }
-
2.dao和mapper
package com.gt.shiro.dao; import com.gt.shiro.entity.testuser; import org.apache.ibatis.annotations.mapper; import java.util.list; @mapper public interface testusermapper { list
findall(); testuser selectone(integer id); testuser selectonebyname(string username); void insert(testuser testuser); void update(testuser testuser); void delete(integer id); }
insert into test_user (id,username,password,perms,role,salt) value (#{id},#{username},#{password},#{perms},#{role},#{salt})
update test_user set username = #{username},password=#{password},perms=#{perms},role=#{role},salt=#{salt} where id = #{id}
delete from test_user where id = #{id}
-
3.业务层及其实现
package com.gt.shiro.server; import com.gt.shiro.entity.testuser; import org.springframework.stereotype.service; import java.util.list; @service public interface testuserserver { /*查询所有*/ list
selectall(); /*查询一个用户*/ testuser selectbyone(integer id); /*通过名字查询一个用户*/ testuser selectonebyname(string name); /*增加一个用户*/ void insert(testuser testuser); /*删除一个用户*/ void delete(integer id); /*更新一个用户*/ void update(testuser testuser); }
package com.gt.shiro.server.serverimpl;
import com.gt.shiro.dao.testusermapper;
import com.gt.shiro.entity.testuser;
import org.apache.shiro.crypto.securerandomnumbergenerator;
import org.apache.shiro.crypto.hash.simplehash;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.stereotype.service;
import com.sunyue.shiro.server.testuserserver;
import java.util.list;
@service
public class testuserserverimpl implements testuserserver {
@autowired
private testusermapper testusermapper;
@override
public list selectall() {
return testusermapper.findall();
}
@override
public testuser selectbyone(integer id) {
return testusermapper.selectone(id);
}
@override
public testuser selectonebyname(string name) {
return testusermapper.selectonebyname(name);
}
@override
public void insert(testuser testuser) {
//加密写法
string salt = new securerandomnumbergenerator().nextbytes().tostring();
string password= new simplehash("md5",testuser.getpassword(),salt,2).tostring();
testuser.setpassword(password);
testuser.setsalt(salt);
testusermapper.insert(testuser);
}
@override
public void delete(integer id) {
testusermapper.delete(id);
}
@override
public void update(testuser testuser) {
testusermapper.update(testuser);
}
}
-
4.控制层
package com.gt.shiro.controller; import com.gt.shiro.entity.testuser; import com.gt.shiro.server.testuserserver; 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.crypto.hash.simplehash; import org.apache.shiro.subject.subject; import org.springframework.beans.factory.annotation.autowired; import org.springframework.stereotype.controller; import org.springframework.ui.model; import org.springframework.web.bind.annotation.*; @controller public class indexcontroller { @autowired private testuserserver testuserserver; @getmapping("/{url}") public string redirect(@pathvariable("url") string url) { return url; } @requestmapping(value = {"/", "/index"}, method = requestmethod.get) private string index() { return "index"; } @postmapping("/login") public string login(string username, string password, model model) { subject subject = securityutils.getsubject(); testuser testuser = testuserserver.selectonebyname(username); if (testuser != null) { //根据salt值和用户输入的密码计算加密后的密码 string salt = testuser.getsalt(); password = new simplehash("md5", password, salt, 2).tostring(); system.out.println(password); } usernamepasswordtoken token = new usernamepasswordtoken(username, password); //usernamepasswordtoken token = new usernamepasswordtoken(username, testuser.getpassword());(不加密写法) try { //将用户名和密码通过token传给shiro进行认证 subject.login(token); testuser user = (testuser) subject.getprincipal(); subject.getsession().setattribute("testuser", user); return "index"; } catch (unknownaccountexception e) { e.printstacktrace(); model.addattribute("msg", "用户名不存在"); return "login"; } catch (incorrectcredentialsexception e) { e.printstacktrace(); model.addattribute("msg", "密码有误"); return "login"; } } @responsebody @getmapping("/unauthor") public string unauthor() { return "权限不足,无法访问"; } @getmapping("/logout") public string logout() { subject subject = securityutils.getsubject(); subject.logout(); return "login"; } @postmapping("/register") public string register(testuser testuser, model model) { string username = testuser.getusername(); string password = testuser.getpassword(); if (username ** null || username.equals("")) { model.addattribute("msg", "用户名不能为空"); return "register"; } else if (password ** null || password.equals("")) { model.addattribute("msg", "密码不能为空"); return "register"; } else if (testuserserver.selectonebyname(username) != null) { model.addattribute("msg", "用户名已被占用"); return "register"; } else { testuserserver.insert(testuser); return "login"; } } }
-
5.前端页面
-
6.数据库文件
/* navicat mysql data transfer source server : sunyue source server version : 50724 source host : localhost:3306 source database : shiro target server type : mysql target server version : 50724 file encoding : 65001 date: 2021-01-11 22:00:47 */ set foreign_key_checks=0; -- ---------------------------- -- table structure for test_user -- ---------------------------- drop table if exists `test_user`; create table `test_user` ( `id` int(11) not null auto_increment, `username` varchar(120) default null, `password` varchar(120) default null, `perms` varchar(120) default null, `role` varchar(120) default null, `salt` varchar(100) default null, primary key (`id`) ) engine=innodb auto_increment=8 default charset=utf8mb4; -- ---------------------------- -- records of test_user -- ---------------------------- insert into `test_user` values ('4', 'admin', '4867df2e009d0096c4cd8d9be8cc104c', 'manage', 'admin', 'gqr2m1n1o3nsljtozmitrq**'); insert into `test_user` values ('5', 'user', '636502f40cf197dd2f4b19f56f475b24', '', '', 'kxw3hzifmgnluu8fmjmy7q**'); insert into `test_user` values ('6', 'user1', '43f3133aa7e0ef9cf8373521dff8d8e8', 'manage', null, 'j8fn4hpauvnorluarl/spg**'); insert into `test_user` values ('7', '1', '1', 'manage', null, null);