一、搭建SpringBoot開發環境
1. 安裝好開發軟件和Maven等
開發工具:Spring Tool Suite(TST)
Maven :3.3.9
jdk:1.8
2. 創建springboot項目(此功能需要聯網)
3. pom.xml文件配置
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.vae</groupId>
<artifactId>shiro</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-shiro</name>
<description>Demo project for Spring Boot</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- thymeleaf依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- Web支持:Spring web開發支持,servlet相關程序等 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 熱部署 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</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>
4. 編寫測試Controller層
package com.vae.user.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Controller
@RequestMapping
public class UserController {
@RequestMapping("/hi")
public String hi(){
return "hi shiro";
}
}
5. 啓動spring boot啓動類
package com.vae;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* springboot啓動類
* @author vae
*
*/
@SpringBootApplication
public class SpringbootShiroApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootShiroApplication.class, args);
}
}
6. 瀏覽器訪問http://localhost:8080/hi 測試
二、使用Thymeleaf頁面模板
1. pom.xml 文件中添加 thymeleaf依賴
<!-- thymeleaf依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
2. 在UserController中編寫測試thymeleaf的方法
下面寫了兩種方式,第一種比較直觀和優雅,第二種相對普遍且代碼較少,且迎合從struts2
注意:第二種方式返回String類型時,controller層不可以使用
@RestController ,@RestController 等價於 @Controller 加上 @ResponseBody, @ResponseBody表示該方法的返回不會被解析爲跳轉, 而是直接寫入http響應正文。
@RequestMapping("/index")
public ModelAndView index() {
ModelAndView view = new ModelAndView();
// 設置跳轉的視圖 默認映射到 src/main/resources/templates/{viewName}.html
view.setViewName("index");
// 設置屬性
view.addObject("title", "我的templates頁面");
view.addObject("desc", "歡迎進入我的csdn博客");
Author author = new Author();
author.setAge(18);
author.setEmail("[email protected]");
author.setName("vae");
view.addObject("author", author);
return view;
}
@RequestMapping("/index1")
public String index1(HttpServletRequest request) {
// TODO 與上面的寫法不同,但是結果一致。
// 設置屬性
request.setAttribute("title", "我的templates頁面");
request.setAttribute("desc", "歡迎進入我的csdn博客");
Author author = new Author();
author.setAge(18);
author.setEmail("[email protected]");
author.setName("vae");
request.setAttribute("author", author);
// 返回的 index 默認映射到 src/main/resources/templates/xxxx.html
return "index";
}
class Author {
private int age;
private String name;
private String email;
// 省略 get set
}
3. 在resources/templates文件夾下新建index.html模板文件
可以看到 thymeleaf
是通過在標籤中添加額外屬性動態綁定數據的
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<!-- 可以看到 thymeleaf 是通過在標籤裏添加額外屬性來綁定動態數據的 -->
<title th:text="${title}">Title</title>
<!-- 在/resources/static/js目錄下創建一個hello.js 用如下語法依賴即可-->
<script type="text/javascript" th:src="@{/js/hello.js}"></script>
</head>
<body>
<h1 th:text="${desc}">Hello World</h1>
<h2>=====作者信息=====</h2>
<p th:text="${author?.name}"></p>
<p th:text="${author?.age}"></p>
<p th:text="${author?.email}"></p>
</body>
</html>
4. 瀏覽器訪問http://localhost:8080/index測試
三、shiro認證-ShiroConfig配置類
1. shiro的核心API
Subject:用戶主體(關聯SecurityManager,把操作交給SecurityManager)
SecurityManager:安全管理器(關聯Realm)
Realm:shiro連接數據庫的橋樑
2. Spring整合shiro
(1)添加shiro和spring整合依賴
<!-- shiro和spring整合依賴 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
(2)自定義Realm類
package com.vae.shiro;
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 arg0) {
System.out.println("執行授權邏輯");
return null;
}
/**
* 認證邏輯
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken arg0) throws AuthenticationException {
System.out.println("執行認證邏輯");
return null;
}
}
(3)編寫shiro配置類(基本結構)
package com.vae.shiro;
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;
/**
* shiro的配置類
* @author Administrator
*
*/
@Configuration
public class ShiroConfig {
/**
* 創建ShiroFilterFactoryBean
*/
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean =new ShiroFilterFactoryBean();
// 設置安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
return shiroFilterFactoryBean;
}
/**
* 創建DefaultWebSecurityManager
*/
@Bean(name="securityManager")
public DefaultWebSecurityManager getdefaultDefaultWebSecurityManager(@Qualifier("userRealm")UserRealm userRealm){
DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();
//關聯Realm
securityManager.setRealm(userRealm);
return securityManager;
}
/**
* 創建Realm
*/
@Bean(name="userRealm")
public UserRealm getRealm(){
return new UserRealm();
}
}
四、shiro認證-使用shiro過濾器實現認證資源攔截
1. 在templates下新建user/add.html和user/update.html
2. 在UserController中編寫訪問這兩個頁面的方法
@RequestMapping("/add")
public String add() {
return "user/add";
}
@RequestMapping("/update")
public String update() {
return "user/update";
}
3. 在index.html頁面中添加這兩個頁面的超鏈接
進入用戶新增頁面:<a href="add">用戶新增</a>
進入用戶更新頁面:<a href="update">用戶更新</a>
測試可訪問。
4. 修改ShiroConfig類的getShiroFilterFactoryBean方法中添加攔截
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean =new ShiroFilterFactoryBean();
//設置安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
//添加shiro內置過濾器,實現權限相關的url攔截
/**
* 常見過濾器:
* anon:無需認證(登錄)可以訪問
* authc:必須認證纔可以訪問
* user:如果使用Remember Me的功能,可以直接訪問
* perms:該資源必須得到資源權限纔可以訪問
* role:該資源必須得到角色權限纔可以訪問
*/
Map<String, String> filterMap=new LinkedHashMap<String, String>();
filterMap.put("/add", "authc");
filterMap.put("/update", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
return shiroFilterFactoryBean;
}
攔截之後重啓,再次訪問發現:試圖訪問add或者update,會自動跳轉到login.jsp頁面
Filter | 解釋 |
---|---|
anon | 無參,開放權限,可以理解爲匿名用戶或遊客 |
authc | 無參,需要認證 |
logout | 無參,註銷,執行後會直接跳轉到shiroFilterFactoryBean.setLoginUrl(); 設置的 url |
authcBasic | 無參,表示 httpBasic 認證 |
user | 無參,表示必須存在用戶,當登入操作時不做檢查 |
ssl | 無參,表示安全的URL請求,協議爲 https |
perms[user] | 參數可寫多個,表示需要某個或某些權限才能通過,多個參數時寫 perms[“user, admin”],當有多個參數時必須每個參數都通過纔算通過 |
roles[admin] | 參數可寫多個,表示是某個或某些角色才能通過,多個參數時寫 roles[“admin,user”],當有多個參數時必須每個參數都通過纔算通過 |
rest[user] | 根據請求的方法,相當於 perms[user:method],其中 method 爲 post,get,delete 等 |
port[8081] | 當請求的URL端口不是8081時,跳轉到schemal://serverName:8081?queryString 其中 schmal 是協議 http 或 https 等等,serverName 是你訪問的 Host,8081 是 Port 端口,queryString 是你訪問的 URL 裏的 ? 後面的參數 |
5. 修改攔截後跳轉的頁面
(1)在templates下新增login.html頁面
(2)在ShiroConfig中shiroFilterFactoryBean方法中修改攔截後跳轉的頁面
//修改跳轉的登錄頁面,不加此項就會跳轉到login.jsp頁面
shiroFilterFactoryBean.setLoginUrl("/toLogin");
(3)在UserController中添加toLogin方法
@RequestMapping("/toLogin")
public String toLogin() {
return "login";
}
測試,當沒有權限跳轉到該login.html頁面
6. 用通配符攔截
filterMap.put("/index", "anon");
filterMap.put("/*", "authc");
五、shiro認證-實現用戶登錄功能
1. 修改完善之前的login.html頁面
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>登錄頁面</title>
</head>
<body>
<h1>登錄頁面</h1>
<span style="color:red" th:text="${msg}"></span>
<form method="post" action="login">
<input type="text" name="username" /><br>
<input type="password" name="password"><br>
<input type="submit" name="submit" value="登錄">
</form>
</body>
</html>
2. 在UserController中編寫/login請求,編寫登錄的處理邏輯
@RequestMapping("/login")
public String login(String username,String password,Model model) {
/**
* 使用shiro編寫認證操作
*/
//獲取Subject
Subject subject=SecurityUtils.getSubject();
//封裝用戶數據
UsernamePasswordToken token=new UsernamePasswordToken(username,password);
//執行登錄方法
try {
//只要執行login方法,就會去執行UserRealm中的認證邏輯
subject.login(token);
//如果沒有異常,代表登錄成功
//跳轉到textThymeleaf頁面,代表主頁
return "redirect:/index";
} catch (UnknownAccountException e) {
e.printStackTrace();
//登錄失敗
model.addAttribute("msg","用戶名不存在");
return "login";
}catch (IncorrectCredentialsException e) {
e.printStackTrace();
model.addAttribute("msg","密碼錯誤");
return "login";
}
}
測試發現,並沒有進入/login請求,是因爲之前寫的攔截器(/*)攔截了所有請求,再對/login請求放行,加入代碼:
filterMap.put("/login", "anon");
重啓測試,發現執行了認證邏輯,返回了用戶名不存在異常。
3. 在UserRealm中編寫shiro認證邏輯
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken arg0) throws AuthenticationException {
System.out.println("執行認證邏輯");
//先寫模擬數據進行驗證,下一步再連接數據庫,假設數據庫的用戶名和密碼如下
String dbusername="vae";
String dbpassword="123456";
//編寫shiro判斷邏輯,判斷用戶名和密碼
//1. 判斷用戶名
UsernamePasswordToken token=(UsernamePasswordToken) arg0;
if (!token.getUsername().equals(dbusername)) {
//用戶名不存在
return null;//shiro底層會拋出UnknownAccountException
}
//2. 判斷密碼
return new SimpleAuthenticationInfo("",dbpassword,"");//參數1:需要返回給login方法的數據;參數2:數據庫密碼,shiro會自動判斷
}
4. 啓動程序,進行測試
六、shiro認證-整合MyBatis完善用戶登錄
1. 導入Mybatis相關的依賴
<!-- druid德魯伊連接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- Spring的MyBatis啓動器 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.1.1</version>
</dependency>
2. 配置application.properties(src/main/resources下)
(1)創建數據庫及用戶表
mysql> CREATE DATABASE springboot_shiro;
Query OK, 1 row affected
mysql> use springboot_shiro;
Database changed
mysql> CREATE TABLE `user` (
`id` int(11) NOT NULL COMMENT 'id',
`username` varchar(20) CHARACTER SET utf8 NOT NULL DEFAULT '' COMMENT '用戶名',
`password` varchar(20) CHARACTER SET utf8 NOT NULL DEFAULT '' COMMENT '密碼',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Query OK, 0 rows affected
(2)配置application.properties
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/springboot_shiro?characterEncoding=UTF-8&useSSL=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root
# 連接池配置
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
# mybatis 別名掃描
mybatis.type-aliases-package=com.fukaiit.domain
3. 編寫User實體類
package com.vae.user.entity;
public class User {
private Integer id;
private String username;
private String password;
//省略get和set方法
}
4. 編寫接口UserDao.java
package com.vae.user.dao;
import com.vae.user.entity.User;
public interface UserDao {
/**
* 根據username查找用戶信息
* @param username
* @return
*/
public User findByName(String username);
}
5. 編寫UserMapper.xml映射文件
<?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">
<!-- 此處指向dao接口 -->
<mapper namespace="com.vae.shiro.user.dao.UserDao">
<!-- 此處指向實體類 -->
<resultMap id="BaseResultMap" type="com.vae.shiro.user.entity.User">
<id column="id" jdbcType="INTEGER" property="id" />
<result column="username" jdbcType="VARCHAR" property="username" />
<result column="password" jdbcType="VARCHAR" property="password" />
</resultMap>
<sql id="Base_Column_List">
id, username, password
</sql>
<!-- 根據username查找用戶信息 -->
<select id="findByUsername" parameterType="java.lang.String" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from user
where username= #{username,jdbcType=VARCHAR}
</select>
</mapper>
6. 編寫UserService.java業務接口
package com.vae.user.service;
import com.vae.user.entity.User;
public interface UserService {
/**
* 根據username查找用戶信息
* @param username
* @return
*/
public User findByUsername(String username);
}
7. 編寫UserServiceImpl.java業務實現類
package com.vae.user.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.vae.user.dao.UserDao;
import com.vae.user.entity.User;
import com.vae.user.service.UserService;
@Service
public class UserServiceImpl implements UserService{
@Autowired
private UserDao userDao;
@Override
public User findByUsername(String username) {
return userDao.findByUsername(username);
}
}
8. 在啓動類添加@MapperScan註解,開啓Mybatis的Mapper接口掃描
@MapperScan("com.vae.**.dao")
9. 修改UserRealm.java,調用剛編寫的業務
@Autowired
private UserService userService;
/**
* 執行認證邏輯
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken arg0) throws AuthenticationException {
System.out.println("執行認證邏輯");
// 編寫shiro判斷邏輯,判斷用戶名和密碼
System.out.println(arg0.toString());
// 1. 判斷用戶名
UsernamePasswordToken token = (UsernamePasswordToken) arg0;
User user = userService.findByUsername(token.getUsername());
if (user==null) {
//用戶名不存在
return null;//shiro底層會拋出UnknownAccountException
}
// 2. 判斷密碼
// 參數1:需要返回給login方法的數據;參數2:數據庫密碼,shiro會自動判斷
return new SimpleAuthenticationInfo("", user.getPassword(), "");
}
10. 測試
效果相同。
七、Shiro授權-使用Shiro過濾器實現授權頁面攔截
1. 在ShiroConfig中添加過濾器
//授權過濾器:授權攔截後,shiro會自動跳轉到未授權頁面
filterMap.put("/add", "perms[user:add]");
filterMap.put("/*", "authc");
Tips:注意要寫在/*之前,否則不會攔截
2. 添加設置未授權頁面
(1)ShiroConfig中
//修改自動跳轉的未授權頁面
shiroFilterFactoryBean.setUnauthorizedUrl("/unAuth");
(2)UserController中
@RequestMapping("/unAuth")
public String unAuth() {
return "unAuth";
}
(3)添加unAuth.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>未授權頁面</title>
</head>
<body>
<h1>抱歉!您無權限訪問!</h1>
</body>
</html>
3. 訪問 http://localhost:8080/add 測試
登錄認證之後,訪問/add頁面會提示未授權,而/update可以正常訪問。
八、Shiro授權-編寫資源授權邏輯
剛纔打印的log日誌中可以看到,只要訪問了需要授權訪問的資源,就會執行UserRealm中的doGetAuthenticationInfo()方法,在該方法中給資源進行授權。
/**
* 執行授權邏輯
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {
System.out.println("執行授權邏輯");
//給資源進行授權
SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
//添加資源的授權字符串
info.addStringPermission("user:add");
return info;
}
測試查看效果:日誌中可以看到執行了該授權邏輯,現在可以訪問/add了
九、Shiro授權-關聯數據庫動態授權
1. 修改數據表
給user表添加perms字段,插入兩個測試用戶
2. 一系列小修改
(1)User.java:添加perms屬性和getter/setter
(2)UserDao.java:
public User findById(Integer id);
(3)UserMapper.xml
<!-- 根據id查找用戶信息 -->
<select id="findById" parameterType="java.lang.Integer" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from user
where id = #{id,jdbcType=INTEGER}
</select>
(4)UserService.java
public User findById(Integer id);
(5)UserServiceImpl.java
@Override
public User findById(Integer id) {
return userMapper.findById(id);
}
(6)給/update添加資源攔截
filterMap.put("/update", "perms[user:update]");
3. 修改UserRealm中的doGetAuthorizationInfo方法
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {
System.out.println("執行授權邏輯");
//給資源進行授權
SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
/*//添加資源的授權字符串
info.addStringPermission("user:add");*/
//獲取當前用戶
Subject subject=SecurityUtils.getSubject();
User user=(User)subject.getPrincipal();
//到數據庫查詢當前登錄用戶的授權字符串
User dbUser=userService.findById(user.getId());//通過當前登錄用戶id查找的數據庫用戶
info.addStringPermission(dbUser.getPerms());
return info;
}
將doGetAuthenticationInfo()方法的返回修改爲
return new SimpleAuthenticationInfo(user,user.getPassword(),"");
因爲User user=(User)subject.getPrincipal(); 所取得的當前登錄用戶就是從這裏來的
4. 登錄不同權限用戶進行測試
各自有了各自的權限。
十、ThymeLeaf和shiro標籤整合使用
1. 導入thymeleaf對shiro的擴展座標
<!-- 導入thymeleaf對shiro的擴展座標 -->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
2. 配置ShiroDialect
ShiroConfig中
@Bean
public ShiroDialect getShiroDialect(){
return new ShiroDialect();
}
3. 在頁面上使用shiro標籤
<div shiro:hasPermission="user:add">
進入用戶新增頁面:<a href="add">用戶新增</a>
</div>
<div shiro:hasPermission="user:update">
進入用戶更新頁面:<a href="update">用戶更新</a>
</div>
4. 運行測試
不同權限用戶登錄,只顯示了他有權限看到的內容。
十一.項目完整目錄
借鑑地址:SpringBoot與Shiro整合-權限管理實戰視頻筆記
重新整理了一邊