来到shiro的官网:shiro官方网站:
下载官方在git仓库上提供的源码,下载后解压:
官方提供了在多种场景下使用的demo,等一下会用到sample里面的东西。下面直接按官方推荐的10分钟入门demo进行测试。
创建一个总的父工程shiro-demo,在父工程下创建一个子模块shiro-demo-01,如下:
父工程下什么都没有,src源目录删掉了,pom.xml也是空的:
父工程pom什么依赖也没有,只包含一个子module。
子模块shiro-demo-01目录如下:
总共只有4个东西,pom.xml如下:
<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>shiro-demo-parentartifactId>
<groupId>com.yczgroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>shiro-demo-01artifactId>
<dependencies>
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-coreartifactId>
<version>1.4.1version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>jcl-over-slf4jartifactId>
<version>1.7.21version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-log4j12artifactId>
<version>1.7.21version>
dependency>
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.17version>
dependency>
dependencies>
project>
log4j.properties和shiro.ini都是从sample目录里的quickstart里面直接复制粘贴过来的。
日志配置文件log4j.properties内容如下:
log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n
# General Apache libraries
log4j.logger.org.apache=WARN
# Spring
log4j.logger.org.springframework=WARN
# Default Shiro logging
log4j.logger.org.apache.shiro=INFO
# Disable verbose logging
log4j.logger.org.apache.shiro.util.ThreadContext=WARN
log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN
shiro配置文件shiro.ini内容如下:
# -----------------------------------------------------------------------------
# Users and their assigned roles
#
# Each line conforms to the format defined in the
# org.apache.shiro.realm.text.TextConfigurationRealm#setUserDefinitions JavaDoc
# -----------------------------------------------------------------------------
[users]
# user 'root' with password 'secret' and the 'admin' role
root = secret, admin
# user 'guest' with the password 'guest' and the 'guest' role
guest = guest, guest
# user 'presidentskroob' with password '12345' ("That's the same combination on
# my luggage!!!" ;)), and role 'president'
presidentskroob = 12345, president
# user 'darkhelmet' with password 'ludicrousspeed' and roles 'darklord' and 'schwartz'
darkhelmet = ludicrousspeed, darklord, schwartz
# user 'lonestarr' with password 'vespa' and roles 'goodguy' and 'schwartz'
lonestarr = vespa, goodguy, schwartz
# -----------------------------------------------------------------------------
# Roles with assigned permissions
#
# Each line conforms to the format defined in the
# org.apache.shiro.realm.text.TextConfigurationRealm#setRoleDefinitions JavaDoc
# -----------------------------------------------------------------------------
[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
src源目录下的Qucikstart也是从quickstart里拷出来的,内容如下:
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.apache.shiro.mgt.SecurityManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @Description
* @ClassName Quickstart
* @Author yanchengzhi
* @date 2021.09.24 13:31
*/
public class Quickstart {
private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);
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", "aValue");
String value = (String) session.getAttribute("someKey");
if (value.equals("aValue")) {
log.info("Shiro==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?
}
}
//getPrincipal方法获取用户认证信息
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!");
}
//登出
currentUser.logout();
System.exit(0);
}
}
运行main方法:
控制台打印的日志信息如上面,到这里快速开始的demo就运行成功了。
下面测试在Springboot中集成Shiro框架,先进行环境的搭建。
在父工程shiro-demo下创建子模块shiro-boot,目录如下:
(1)pom依赖
<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.0modelVersion>
<groupId>com.yczgroupId>
<artifactId>shiro-bootartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>shiro-bootname>
<description>Demo project for Spring Bootdescription>
<properties>
<java.version>1.8java.version>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
<spring-boot.version>2.3.7.RELEASEspring-boot.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<optional>trueoptional>
<scope>truescope>
dependency>
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-springartifactId>
<version>1.7.1version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
<exclusions>
<exclusion>
<groupId>org.junit.vintagegroupId>
<artifactId>junit-vintage-engineartifactId>
exclusion>
exclusions>
dependency>
dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-dependenciesartifactId>
<version>${spring-boot.version}version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<version>3.8.1version>
<configuration>
<source>1.8source>
<target>1.8target>
<encoding>UTF-8encoding>
configuration>
plugin>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
<version>2.3.7.RELEASEversion>
<configuration>
<mainClass>com.ycz.shiroboot.ShiroBootApplicationmainClass>
configuration>
<executions>
<execution>
<id>repackageid>
<goals>
<goal>repackagegoal>
goals>
execution>
executions>
plugin>
plugins>
build>
project>
(2)application.properties配置
# 应用名称
spring.application.name=shiro-boot
# 应用服务 WEB 访问端口
server.port=8080
# THYMELEAF (ThymeleafAutoConfiguration)
# 开启模板缓存(默认值: true )
spring.thymeleaf.cache=false
# 检查模板是否存在,然后再呈现
spring.thymeleaf.check-template=true
# 检查模板位置是否正确(默认值 :true )
spring.thymeleaf.check-template-location=true
#Content-Type 的值(默认值: text/html )
spring.thymeleaf.content-type=text/html
# 开启 MVC Thymeleaf 视图解析(默认值: true )
spring.thymeleaf.enabled=true
# 模板编码
spring.thymeleaf.encoding=UTF-8
# 要被排除在解析之外的视图名称列表,⽤逗号分隔
spring.thymeleaf.excluded-view-names=
# 要运⽤于模板之上的模板模式。另⻅ StandardTemplate-ModeHandlers( 默认值: HTML5)
spring.thymeleaf.mode=HTML5
# 在构建 URL 时添加到视图名称前的前缀(默认值: classpath:/templates/ )
spring.thymeleaf.prefix=classpath:/templates/
# 在构建 URL 时添加到视图名称后的后缀(默认值: .html )
spring.thymeleaf.suffix=.html
(3)shiro配置
在config包下是关于Shiro的关键配置:
先自定义所需的Realm类,只需继承AuthorizingRealm类即可,内容如下:
package com.ycz.shiroboot.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;
/**
* @Description 自定义Realm,继承AuthorizingRealm类,重写认证和授权方法
* @ClassName UserRealm
* @Author yanchengzhi
* @date 2021.09.25 15:36
*/
public class UserRealm extends AuthorizingRealm {
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("进入授权方法!");
return null;
}
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("进入认证方法!");
return null;
}
}
再配置ShiroConfig类,这个类主要是用来向Spring容器中注入bean的,内容如下:
package com.ycz.shiroboot.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;
/**
* @Description Shiro的配置类
* @ClassName ShiroConfig
* @Author yanchengzhi
* @date 2021.09.25 15:35
*/
@Configuration
public class ShiroConfig {
/*
* @description: 注入UserRealm的bean
* @param: []
* @return: com.ycz.shiroboot.config.UserRealm
* @author: yanchengzhi
* @date: 2021/9/25 15:42
*/
@Bean
public UserRealm userRealm() {
return new UserRealm();
}
/*
* @description: 注入DefaultWebSecurityManager的bean,需要依赖定义的UserRealm对象
* @param: [userRealm]
* @return: org.apache.shiro.web.mgt.DefaultWebSecurityManager
* @author: yanchengzhi
* @date: 2021/9/25 15:47
*/
@Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 关联realm
securityManager.setRealm(userRealm);
return securityManager;
}
/*
* @description: 注入ShiroFilterFactoryBean的bean,需要依赖DefaultWebSecurityManager对象
* @param: [defaultWebSecurityManager]
* @return: org.apache.shiro.spring.web.ShiroFilterFactoryBean
* @author: yanchengzhi
* @date: 2021/9/25 15:52
*/
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(
@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
// 设置安全管理器
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
return shiroFilterFactoryBean;
}
}
(4)准备页面
准备所需的几个页面,目录层次如下:
index.html直接在templates目录下,而add.html和edit.html在user目录下。
index.html内容如下:
DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>index页面title>
head>
<body>
<h1>欢迎来到首页!h1>
<h2 style="color: red;" th:text="${key}">h2>
<a th:href="@{/user/add}">添加用户a> | <a th:href="@{/user/edit}">修改用户a>
body>
html>
add.html内容如下:
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>用户添加页面title>
head>
<body>
<h1>添加页面h1>
body>
html>
edit.html内容如下:
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>用户修改页面title>
head>
<body>
<h1>修改页面h1>
body>
html>
(5)控制器
在controller包下创建一个TestController类控制器:
具体内容如下:
package com.ycz.shiroboot.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* @Description
* @ClassName TestController
* @Author yanchengzhi
* @date 2021.09.25 15:10
*/
@Controller
@RequestMapping("/")
public class TestController {
/*
* @description: 默认跳转
* @param: [model]
* @return: java.lang.String
* @author: yanchengzhi
* @date: 2021/9/25 16:20
*/
@RequestMapping("")
public String toIndexPage(Model model) {
model.addAttribute("key","Shiro安全框架");
return "index";
}
/*
* @description: 跳往添加页面
* @param: []
* @return: java.lang.String
* @author: yanchengzhi
* @date: 2021/9/25 16:21
*/
@RequestMapping("/user/add")
public String toAddPage() {
return "/user/add";
}
/*
* @description: 跳往编辑修改页面
* @param: []
* @return: java.lang.String
* @author: yanchengzhi
* @date: 2021/9/25 16:22
*/
@RequestMapping("/user/edit")
public String toEditPage() {
return "/user/edit";
}
}
(6)测试
启动Springboot项目,访问:http://localhost:8080/。
成功跳转到了默认页面index.html,点击添加用户:
跳转到了添加页面,再点击修改用户:
跳转到了修改页面。OK,到这里环境就搭建完成了,先保证项目能够跑得起来。下面将进一步的配置和测试。
shiro中请求的拦截是通过ShiroFilterFactoryBean对象来完成的,刚才其实在ShiroConfig中已经配置该类型的bean了,只不过有的配置内容没有加进去。下面在之前的基础上添加一些配置:
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(
@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
// 设置安全管理器
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
// 添加Shiro常用的过滤器
/**
* 常用过滤器如下:
* anon:无需认证即可访问
* authc:必须经过认证才能访问
* user:使用记住我后可访问
* perms:拥有某个资源的权限才可访问
* role:拥有某个角色才可访问
*
*/
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
// filterChainDefinitionMap.put("/user/add","authc");
// filterChainDefinitionMap.put("/user/edit","authc");
// 支持通配符配置
filterChainDefinitionMap.put("/user/*","authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
// 设置登录页面,没有认证或权限的会被定位到此路径
shiroFilterFactoryBean.setLoginUrl("/toLogin");
return shiroFilterFactoryBean;
}
然后在templates目录下创建一个登录页面login.html,内容如下:
DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>登录页面title>
head>
<body>
<h3>用户登录h3>
<form th:action="@{/doLogin}" method="post">
<p>用户名:<input type="text" name="username" />p>
<p>密码:<input type="password" name="password" />p>
form>
body>
html>
控制器中加一个跳转的方法:
启动项目进行测试:
点击添加用户:
被定位到了登录页面。再点击修改用户:
同样的被定位到了登录页面。原因是在过滤器中的配置:
/user/下的所有路径访问都需要经过认证,因为设置成了authc,对于没有认证的,将定位到登录Url。
在控制器里面加一个登录校验的方法,如下:
/*
* @description: 登录校验
* @param: [username, password, model]
* @return: java.lang.String
* @author: yanchengzhi
* @date: 2021/9/25 17:16
*/
@RequestMapping("/doLogin")
public String doLogin(@RequestParam("username") String username,
@RequestParam("password") String password,
Model model) {
// 获取当前用户
Subject currentUser = SecurityUtils.getSubject();
// 如果当前用户未进行认证
if(!currentUser.isAuthenticated()) {
// 将用户名和密码加密封装为token
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
try {
// 执行登录
currentUser.login(token);
} catch (UnknownAccountException uae) { // 用户名不存在
model.addAttribute("errorMsg","用户名不存在!");
return "login";
} catch (IncorrectCredentialsException ice) { // 密码错误
model.addAttribute("errorMsg", "密码错误!");
return "login";
}
}
return "index";
}
登录页面login.html修改如下:
登录的逻辑很简单,先获取到当前用户,如果用户没有进行认证,将前端提交过来的用户名和密码进行加密封装为一个token,然后用户带着这个token执行login方法登录。而login方法经过一系列的操作,最终会将这个token传到我们自定义UserRealm类里的doGetAuthenticationInfo方法里面:
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("进入认证方法!");
// 用户名和密码,应该从数据库中取,这里用静态数据代替
String name = "ycz";
String password = "ycz123456";
// 将token进行转换,token是前端页面传过来的用户名+密码的加密封装
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
// 用户名校验
if(!usernamePasswordToken.getUsername().equals(name)) {
return null; // 返回null的话,会自动抛出UnknownAccountException异常
}
// 密码校验,Shiro自动完成
return new SimpleAuthenticationInfo("",password,"");
}
然后在这个重写方法里进行用户名和密码的校验,用户名由我们自己校验,而密码则由Shiro来自动进行校验,非常安全和简单。启动项目访问进行测试。
先用错误的用户名进行登录:
控制台:
再用正确的用户名和错误的密码进行登录:
控制台:
最后用正确的用户名和正确的密码进行登录:
控制台:
登录成功,成功来到了首页。
上面写的用户名和密码是死数据,实际上会从数据库中来查询。下面将mybatis整合进来,以便于从数据库中获取数据。
(1)pom依赖
在pom.xml中添加以下依赖坐标:
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.11version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.12version>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.1.3version>
dependency>
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.17version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.20version>
<scope>providedscope>
dependency>
(2)application.yml全局配置
将前面的application.propertis内容全部放到新建的application.yml配置文件中,删除application.propertis,配置好的application.yml内容如下:
server:
port: 8080
spring:
application:
name: shiro-boot
## 配置thymeleaf模板
thymeleaf:
cache: false
check-template: true
check-template-location: true
servlet:
content-type: text/html
enabled: true
encoding: UTF-8
mode: HTML5
prefix: classpath:/templates/
suffix: .html
## 配置数据源
datasource:
url: jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: ycz123456
# 初始连接数
initialSize: 5
# 最小连接池数量
minIdle: 10
# 最大连接池数量
maxActive: 20
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
# 配置一个连接在池中最大生存的时间,单位是毫秒
maxEvictableIdleTimeMillis: 900000
# 配置检测连接是否有效
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
## mybatis配置
mybatis:
## 实体类路径
type-aliases-package: com.ycz.domain
## 映射mapper位置
mapper-locations: classpath:mapper/*.xml
(3)建表
mysql数据库中建user表:
表建好后,添加几条数据:
(4)实体类
package com.ycz.shiroboot.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @Description
* @ClassName User
* @Author yanchengzhi
* @date 2021.09.25 18:48
*/
@Data
@AllArgsConstructor // 全参注解
@NoArgsConstructor // 无参注解
public class User {
private Integer id;
private String username;
private String password;
}
(5)mapper层和映射文件
package com.ycz.shiroboot.mapper;
import com.ycz.shiroboot.domain.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
@Repository
@Mapper
public interface UserMapper {
User findByUsername(@Param("username") String username);
}
在resources下创建mapper目录,下面创建UserMapper.xml映射文件:
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ycz.shiroboot.mapper.UserMapper">
<select id="findByUsername" parameterType="String" resultType="com.ycz.shiroboot.domain.User">
select * from user where username = #{username}
select>
mapper>
(6)service层和实现类
package com.ycz.shiroboot.service;
import com.ycz.shiroboot.domain.User;
public interface UserService {
// 通过用户名查询用户
User findByUsername(String username);
}
package com.ycz.shiroboot.service.impl;
import com.ycz.shiroboot.domain.User;
import com.ycz.shiroboot.mapper.UserMapper;
import com.ycz.shiroboot.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @Description
* @ClassName UserServiceImpl
* @Author yanchengzhi
* @date 2021.09.25 18:55
*/
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public User findByUsername(String username) {
return userMapper.findByUsername(username);
}
}
(7)修改UserRealm
修改自定义类UserRealm里的认证方法,现在从数据库中进行查询,而不使用死数据:
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("进入认证方法!");
// 将token进行转换,token是前端页面传过来的用户名+密码的加密封装
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
User user = userService.findByUsername(usernamePasswordToken.getUsername());
if(user == null) { // 用户名校验
return null;
}
// 密码校验,Shiro自动完成
return new SimpleAuthenticationInfo("",user.getPassword(),"");
}
(8)其他修改
过滤器里添加一个登出配置,登出是shiro自带的:
首页加一个登出按钮:
这个路径/shiro/logout不是我的控制器里加的,而是shiro提供的,直接用就可以了,退出后shiro会清掉用户的缓存。
(9)测试
启动项目,到登录页面进行测试:
用数据库中不存在的用户名进行登录:
用正确的用户名和错误密码进行登录:
最后用正确的用户名和正确的密码进行登录:
OK,登录成功,来到首页,说明整合mybatis是成功的。
用户认证成功后才会进行授权,也就是用户是先进行认证后进行授权的,以下将对shiro的授权进行测试。
数据库表加一个字段用来存权限,实际上权限是单独建表存的,这里为了简单,直接加在user表里。
对应的User实体类也加一个属性:
修改认证方法的返回:
这个的第一个参数改为user,也就是从数据库中获取出来的,也就是即将登录的用户信息。然后修改授权方法:
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("进入授权方法!");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 获取当前登录用户
Subject subject = SecurityUtils.getSubject();
User currentUser = (User)subject.getPrincipal();
info.addStringPermission(currentUser.getPers());
return info;
}
修改过滤器里的配置:
这里添加了两条新的路径过滤配置,且采用了perms过滤器,即拥有权限才能访问。并且设置了没有权限的重定向url:
控制器里加一个方法:
/*
* @description: 没有权限的跳转
* @param: []
* @return: java.lang.String
* @author: yanchengzhi
* @date: 2021/9/25 20:27
*/
@ResponseBody
@RequestMapping("/noAuth")
public String noAuth() {
return "您没有权限访问该资源!";
}
启动项目进行测试:
yanchengzhi这个用户是只有添加权限没有修改权限的,登录,点击添加用户:
可以访问,再点击修改用户:
退出,以另一个账号登录进去:
云过梦无痕这个用户没有添加权限也没有修改权限。登录成功后点击添加用户:
再点击修改用户:
测试是OK的,到这里说明权限的配置也是成功的,其他的账号就不进行测试了。
我们经常会在thymeleaf模板中根据权限信息来隐藏或显示某个按钮,这就需要在thymelaf中整合shiro,使用shiro标签进行控制。
(1)引入依赖
需要在pom中先引入thymelaf整合shiro的依赖:
<dependency>
<groupId>com.github.theborakompanionigroupId>
<artifactId>thymeleaf-extras-shiroartifactId>
<version>2.0.0version>
dependency>
需要注意版本,可能会和其他包版本有冲突,注意一下即可。
(2)配置ShiroDialect
需要在ShiroConfig中配置shiro整合thymeleaf的方言:
/*
* @description: shiro整合thymeleaf
* @param: []
* @return: at.pollux.thymeleaf.shiro.dialect.ShiroDialect
* @author: yanchengzhi
* @date: 2021/9/25 20:46
*/
@Bean
public ShiroDialect getShiroDialect(){
return new ShiroDialect();
}
(3)存Session
在用户登录成功后,可以往Session里存一些信息,这里的Session是Shiro提供的,而不是HttpSession。在登录方法加:
登录成功后,将当前用户存在了Session里。
(4)模板中使用shiro标签
在thymelaf中使用shiro标签,需要先引入命名空间:
然后再使用标签进行资源的显示和隐藏控制。修改后的index.html如下:
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>index页面title>
head>
<body>
<h1>欢迎来到首页!h1>
<div th:if="${session.currentUser == null}">
<a th:href="@{/toLogin}">登录a>
div>
<h2 style="color: red;" th:text="${key}">h2>
<div shiro>div>
<div shiro:hasPermission="user:add">
<a th:href="@{/user/add}">添加用户a> |
div>
<div shiro:hasPermission="user:update">
<a th:href="@{/user/edit}">修改用户a> |
div>
<a th:href="@{/shiro/logout}">退出a>
body>
html>
(5)测试
启动项目进行测试:
现在没有登录,是看不到添加用户和修改用户的,点击登录,使用yanchengzhi账号登录进去:
只显示除了添加用户链接,退出,再以另一个账号登录:
只有修改用户。可以看到,这些都是使用shiro的标签来进行控制的,通过使用标签,来判断用户是否拥有某项权限,从而来限制用户的操作,这在实际开发中非常常见。
这只是springboot整合shiro框架的一个简单入门,实际开发比这复杂的多,包括密码的加密,权限的配置拦截等,但是万变不离其宗,只要入门了,那么再复杂的都是以这为基础,学起来就会相对简单一点了。