一 、shiro框架
Shiro是Apache 的一個強大且易用的Java安全框架,執行身份驗證、授權、密碼學和會話管理。Shiro 主要分為兩個部分就是認證和授權兩部分
1.Subject代表了當前用戶的安全操作
2.SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro通過SecurityManager來管理內部組件實例,並通過它來提供安全管理的各種服務。
3.Authenticator即認證器,對用戶身份進行認證,Authenticator是一個接口,shiro提供ModularRealmAuthenticator實現類,通過ModularRealmAuthenticator基本上可以滿足大多數需求,也可以自定義認證器。
4.Authorizer即授權器,用戶通過認證器認證通過,在訪問功能時需要通過授權器判斷用戶是否有此功能的操作權限。
5.Realm充當了Shiro與應用安全數據間的“橋梁”或者“連接器”。也就是說,當對用戶執行認證(登錄)和授權(訪問控制)驗證時,Shiro會從應用配置的Realm中查找用戶及其權限信息。
6.sessionManager即會話管理,shiro框架定義了一套會話管理,它不依賴web容器的session,所以shiro可以使用在非web應用上。
二:引入依賴
<?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.example</groupId>
<artifactId>ch06</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>ch06</name>
<description>Demo project for Spring Boot</description>
<packaging>jar</packaging>
<properties>
<java.version>1.8</java.version>
<druid.verzion>1.1.10</druid.verzion>
<pagehelper.version>1.2.10</pagehelper.version>
<mybatis.version>2.0.0</mybatis.version>
<thymeleaf-layout-dialect.version>2.0.4</thymeleaf-layout-dialect.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- 排除默認的tomcat -->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 重新依賴Jetty的starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
<!--shiro整合spring-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.verzion}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper-spring-boot-starter -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>${pagehelper.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- spring boot maven插件 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!-- 跳過單元測試 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
</plugins>
</build>
</project>
二:yml配置:
1.application-dao.yml
# spring整合配置 spring: # 數據源配置 datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/weather?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC username: root password: root # 使用druid連接池,當使用其他連接池的時候,可以指定type類型為對應的數據源 type: com.alibaba.druid.pool.DruidDataSource druid: maxActive: 1000 initialSize: 10 maxWait: 1000 minIdle: 10 timeBetweenEvictionRunsMillis: 60000 minEvictableIdleTimeMillis: 300000 validationQuery: select 1 testWhileIdle: true testOnBorrow: false testOnReturn: false maxPoolPreparedStatementPerConnectionSize: 0 # 監控 stat-view-servlet: url-pattern: /druid/* reset-enable: false login-username: admin login-password: admin allow: 127.0.0.1 web-stat-filter: url-pattern: /* exclusions: /druid/*,*.js,*.css,*.html,*.png,*.jpg # mybatis配置 mybatis: type-aliases-package: edu.nf.ch06.entity mapper-locations: classpath:/mapper/*.xml # 分頁插件配置 pagehelper: # 數據庫方言 helper-dialect: mysql # 分頁合理化參數,默認值為false。當該參數設置為 true 時,pageNum<=0 時會查詢第一頁 reasonable: true # 分頁參數 support-methods-arguments: true
2.application-dao.yml
# web容器配置
server:
# 設置tomcat(如果切換了Jetty,
# 那么這里換成Jetty的相關配置,並把tomcat的配置注釋)
#tomcat:
#uri-encoding: UTF-8
# 指定端口
port: 8081
# 指定項目的ContextPath路徑,8080后面緊跟着的項目名
servlet:
context-path: /ch06
spring:
# 配置http字符編碼過濾器配置(CharacterEncodingFilter)
http:
encoding:
charset: UTF-8
enabled: true
force: true
# jackson日期格式化和時區設置
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
三:導入數據庫(這里用city_name當做用戶名,city_code做為密碼)
/* Navicat Premium Data Transfer Source Server : 192.168.5.8 Source Server Type : MySQL Source Server Version : 50723 Source Host : localhost:3306 Source Schema : weather Target Server Type : MySQL Target Server Version : 50723 File Encoding : 65001 Date: 12/07/2019 16:08:07 */
SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for city_info -- ----------------------------
DROP TABLE IF EXISTS `city_info`; CREATE TABLE `city_info` ( `city_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '城市編號', `city_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '城市名稱', `city_code` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '城市編碼', `province` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '省份', `url` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, PRIMARY KEY (`city_id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of city_info -- ----------------------------
INSERT INTO `city_info` VALUES (1, '江西', '123', NULL, 'add:user'); INSERT INTO `city_info` VALUES (2, '廣東', '1234', NULL, 'update:user'); SET FOREIGN_KEY_CHECKS = 1;
四:Controller層
package edu.nf.ch06.controller; import edu.nf.ch06.entity.City; import edu.nf.ch06.service.CityService; import edu.nf.ch06.vo.ResponseVO; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authz.annotation.Logical; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.apache.shiro.authz.annotation.RequiresRoles; import org.apache.shiro.subject.Subject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; /** * @author ywb * @date 2019-07-08 */ @RestController public class CityController extends BaseController{ @Autowired private CityService cityService; @GetMapping("/list_city") public ResponseVO listCity(){ List<City> list = cityService.listCity(); return success(list); }
@RequestMapping("/add") public String add(){ System.out.println("具有添加權限"); return "具有add權限"; } @RequestMapping("/update") public String update(){ System.out.println("具有修改權限"); return "具有update權限"; } @RequestMapping("/test") @RequiresPermissions(value={"weather:city:test","super"}) public String test(){ System.out.println("測試權限"); return "測試注解RequiresPermissions是否有用"; } @RequestMapping("/index") @RequiresRoles(value={"江西"}) public String index(){ System.out.println("修改"); return "江西登入的用戶有此權限"; } @RequestMapping("/unAuth") public String unAuth(){ System.out.println("修改"); return "未經授權,無法訪問此頁面"; } @RequestMapping("/login") public String login(String cityName, String cityCode, Model model){ Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken(cityName,cityCode); try { subject.login(token); return "登入成功"; } catch (UnknownAccountException e) { model.addAttribute("msg","用戶名不存在"); return "用戶名錯誤"; } catch (IncorrectCredentialsException e) { model.addAttribute("msg","密碼錯誤"); return "密碼錯誤"; } } }
五:Shiro配置類
package edu.nf.ch06.shiro; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; 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.context.annotation.DependsOn; import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver; import java.util.LinkedHashMap; import java.util.Map; import java.util.Properties; /** * @author : ywb * @date : 2019/7/8 */ @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:該資源必須得到權限資源脆才能訪問 */ Map<String,String> filterMap =new LinkedHashMap<String,String>(); // filterMap.put("/list_city","authc"); // filterMap.put("/add","anon"); // filterMap.put("/update","anon");
filterMap.put("/login","anon"); filterMap.put("/add","perms[add:user]"); filterMap.put("/update","perms[update:user]"); // filterMap.put("list_city","authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap); //授權過濾器 //注意當授權攔截后,shiro會自動跳轉到未授權的頁面 //修改調整的登入頁面 // shiroFilterFactoryBean.setLoginUrl(""); //登入失敗之后需要跳轉的頁面或需要請求的接口
shiroFilterFactoryBean.setUnauthorizedUrl("/unAuth"); return shiroFilterFactoryBean; } @Bean(name = "securityManager") public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("weatherRealm") WeatherRealm weatherRealm){ DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager(); //DefaultWebSecurityManager需要關聯一個Realm
defaultWebSecurityManager.setRealm(weatherRealm); return defaultWebSecurityManager; } /** * 創建realm */ @Bean(name = "weatherRealm") public WeatherRealm getRealm(){ return new WeatherRealm(); } /** * 開啟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; } }
2.WeatherRealm.java
package edu.nf.ch06.shiro; import edu.nf.ch06.dao.CityDao; import edu.nf.ch06.entity.City; import edu.nf.ch06.service.CityService; 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.List; /** * @author : ywb * @date : 2019/7/8 */
public class WeatherRealm extends AuthorizingRealm { @Autowired private CityService cityService; /** * 執行授權邏輯 * @param principalCollection * @return
*/ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("執行授權邏輯"); //給資源進行授權
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); // info.addStringPermission("user:add");
List<String> roles = new ArrayList<>(); Subject subject = SecurityUtils.getSubject(); City city = (City)subject.getPrincipal(); City dbCity = cityService.findUserById(city.getCityId()); info.addStringPermission(dbCity.getUrl()); roles.add(dbCity.getCityName()); info.addRoles(roles); return info; } /** * 執行認證邏輯 * @param authenticationToken * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { // String userName = "ywb"; // String password = "123";
UsernamePasswordToken passwordToken = (UsernamePasswordToken)authenticationToken; City city = cityService.getCityByName(passwordToken.getUsername()); //取出用戶名並且判斷用戶名是否和數據庫一致
if(city==null){ return null;//Shiro底層有拋出一個異常表示用戶名不存在
} System.out.println("執行認證邏輯"); //判斷密碼
return new SimpleAuthenticationInfo(city,city.getCityCode(),""); } }
六:測試
1.通過用戶名:江西 登入的用戶具有添加沒有修改權限
2.通過用戶名:廣東登入的用戶具有修改沒有添加權限
3.需要了解更多進入:http://shiro.apache.org/