Shiro安全框架

1.簡介

  • Apache Shiro 是一個功能強大且易于使用的 Java 安全(權(quán)限)框架。
  • Shiro 可以完成:認證、授權(quán)、加密、會話管理、與 Web 集成、緩存等。借助 Shiro 您可以快速輕松地保護任何應(yīng)用程序,從最小的移動應(yīng)用程序到最大的 Web 和企業(yè)應(yīng)用程序。

2.使用Shiro的好處

  • 易于使用:Shiro構(gòu)建系統(tǒng)安全框架非常簡單,易于上手掌握。
  • 全面:Shiro 包含系統(tǒng)安全框架需要的功能,滿足安全需求的“一站式服務(wù)”。
  • 適應(yīng)靈活:在任何程序都可以應(yīng)用,也沒有強制要求任何規(guī)范,甚至沒有很多依賴項。
  • 兼容性強:Shiro 的設(shè)計模式使其易于與其他框架和應(yīng)用程序集成。Shiro 與 Spring、Grails、Wicket、Tapestry、Mule、Apache Camel、Vaadin 等框架無縫集成。
  • Shiro 是 Apache 軟件基金會的一個開源項目,有完備的社區(qū)支持,文檔支持。

3.三大核心

Subject

應(yīng)用代碼直接交互的對象是 Subject,是Shiro 的對外 API 核心,代表“當前操作的用戶”,這個用戶不僅僅指的是人,也可以是其他交互的事物。

SecurityManager

安全管理器;即所有與安全有關(guān)的操作都會與 SecurityManager交互;且其管理著所有 Subject、并負責(zé)進 行認證、授權(quán)、會話及緩存的管理 ;可以看出它是 Shiro 的核心。

Realm

Shiro 從 Realm 獲取安全數(shù)據(jù)(如用戶、角色、權(quán)限),就是說SecurityManager 要驗證用戶身份,那么它需要從 Realm 獲取相應(yīng)的用戶進行比較以確定用戶身份是否合法;也需要從 Realm 得到用戶相應(yīng)的角色/ 權(quán)限進行驗證用戶是否能進行操作;可以把 Realm 看成 DataSource。

4.Shiro 應(yīng)用實例

4.1.開發(fā)組件
名稱 版本
HighGoDB 安全版V4.5、企業(yè)版V5及以上
HgdbJdbc 6.2.4
JDK 1.6、1.7、1.8
Java IDE Eclipse、IntelliJ IDEA
SpringBoot 1.5.9.RELEASE
MybatisPlus 2.1.4
Druid 1.2.6
Shiro 1.3.2
4.2.工程結(jié)構(gòu)示例

image

4.3.主要文件

pom.xml

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.9.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<mybatisplus-spring-boot-starter.version>1.0.5</mybatisplus-spring-boot-starter.version>
<mybatisplus.version>2.1.4</mybatisplus.version>
<shiro-spring.version>1.3.2</shiro-spring.version>
<druid.version>1.2.6</druid.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--沒有此依賴shiro注解權(quán)限會失效-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<!--lombok插件使用依賴-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--mybatis plus依賴-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatisplus-spring-boot-starter</artifactId>
<version>${mybatisplus-spring-boot-starter.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<version>${mybatisplus.version}</version>
</dependency>
<!--數(shù)據(jù)源相關(guān)-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.version}</version>
</dependency>

<dependency>
<groupId>com.highgo</groupId>
<artifactId>HgdbJdbc</artifactId>
<version>6.2.4</version>
</dependency>
<!--shiro依賴-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>${shiro-spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--熱部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>springloaded</artifactId>
</dependency>
</dependencies>

application.yml

server:
port: 8080
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.highgo.jdbc.Driver
url: jdbc:highgo://xxxx:5866/highgo?currentSchema=shiro_demo
username: sysdba
password: xxxx
druid:
# 初始化連接數(shù)量
initial-size: 5
# 最小線連接數(shù)量
min-idle: 5
# 最大連接數(shù)量
max-active: 20
# 獲取連接時最大等待時間,單位毫秒
max-wait: 60000
#銷毀線程時檢測當前連接的最后活動時間和當前時間差大于該值時,關(guān)閉當前連接
min-evictable-idle-time-millis: 30000
#用來檢測連接是否有效的sql 必須是一個查詢語句
validation-query: select 1
#申請連接時會執(zhí)行validationQuery檢測連接是否有效,開啟會降低性能,默認為true
test-on-borrow: false
#歸還連接時會執(zhí)行validationQuery檢測連接是否有效,開啟會降低性能,默認為tru
test-on-return: false
# 配置監(jiān)控統(tǒng)計攔截的filters,去掉后監(jiān)控界面sql無法統(tǒng)計,'wall'用于防火墻
# 配置監(jiān)拉統(tǒng)計擋成的filters. stat: 監(jiān)控統(tǒng)計、Log4j:日志記錄、waLL: 防御sqL注入
filters: stat,wall,log4j
db-type: postgresql
mybatis-plus:
mapper-locations: classpath:mappers/*.xml
type-aliases-package: com.highgo.entity
global-config:
db-column-underline: true
configuration:
#開啟sql日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

ShiroConfig.java

package com.highgo.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.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;

@Configuration
public class ShiroConfig {

//將自己的驗證方式加入容器
@Bean
public MyShiroRealm myShiroRealm() {
MyShiroRealm myShiroRealm = new MyShiroRealm();
return myShiroRealm;
}

//權(quán)限管理,配置主要是Realm的管理認證
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myShiroRealm());
return securityManager;
}

//Filter工廠,設(shè)置對應(yīng)的過濾條件和跳轉(zhuǎn)條件
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

// 必須設(shè)置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);

// 如果不設(shè)置默認會自動尋找Web工程根目錄下的"/login.jsp"頁面
shiroFilterFactoryBean.setLoginUrl("/login");
// 登錄成功后要跳轉(zhuǎn)的鏈接
shiroFilterFactoryBean.setSuccessUrl("/index");

// 權(quán)限控制map.
LinkedHashMap<String, String> filterChainDefinitionMap=new LinkedHashMap<>();
filterChainDefinitionMap.put("/css/**", "anon"); //表示可以匿名訪問
filterChainDefinitionMap.put("/js/**", "anon"); //表示可以匿名訪問
filterChainDefinitionMap.put("/img/**", "anon"); //表示可以匿名訪問
filterChainDefinitionMap.put("/font/**", "anon"); //表示可以匿名訪問
filterChainDefinitionMap.put("/images/**", "anon"); //表示可以匿名訪問
filterChainDefinitionMap.put("/login", "anon"); //表示可以匿名訪問
filterChainDefinitionMap.put("/user_login", "anon");
filterChainDefinitionMap.put("/logout*","logout");
filterChainDefinitionMap.put("/error","anon");
filterChainDefinitionMap.put("/**","authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}

//加入注解的使用,不加入這個注解不生效
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
System.out.println("開啟了shiro注解功能");
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}

MyShiroRealm.java

package com.highgo.shiro;

import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.highgo.entity.*;
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 java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

//實現(xiàn)AuthorizingRealm接口用戶用戶認證
public class MyShiroRealm extends AuthorizingRealm {

//角色權(quán)限和對應(yīng)權(quán)限添加
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//添加角色和權(quán)限
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
System.out.println(principalCollection.getPrimaryPrincipal());
TUser user= (TUser) principalCollection.getPrimaryPrincipal();
//保存所有角色名
Set<String> allRoles = new HashSet<>();
//保存所有權(quán)限名
Set<String> allPermissions = new HashSet<>();
//查詢對應(yīng)角色
List<TUserRole> secUserRoles = new TUserRole().selectList(new EntityWrapper().eq("user_id", user.getId()));
for (TUserRole userRole:secUserRoles) {
TRole role = new TRole();
role.setId(userRole.getRoleId());
role = role.selectById();
allRoles.add(role.getName());

//查詢所有權(quán)限
List<TPermission> permissions = new ArrayList<>();
List<TRolePermission> rolePermissions = new TRolePermission().selectList(new EntityWrapper().eq("role_id", role.getId()));
for (TRolePermission rolePermission:rolePermissions) {
TPermission permission = new TPermission();
permission.setId(rolePermission.getPermissionId());
permission = permission.selectById();
allPermissions.add(permission.getName());
}
}
//添加角色
simpleAuthorizationInfo.addRoles(allRoles);
simpleAuthorizationInfo.addStringPermissions(allPermissions);

System.out.println(user);

return simpleAuthorizationInfo;
}

//用戶認證
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 1、登錄認證的方法需要先執(zhí)行,需要用他來判斷登錄的用戶信息是否合法
String username = (String) token.getPrincipal(); // 取得用戶名
// 需要通過用戶名取得用戶的完整信息,利用業(yè)務(wù)層操作
TUser user = null;
try {
user = new TUser().selectOne(new EntityWrapper().eq("username",username));
} catch (Exception e) {
e.printStackTrace();
}
if (user == null) {
throw new UnknownAccountException("該用戶名稱不存在!");
} else { // 進行密碼的驗證處理
String password =new String((char[]) token.getCredentials());
// 將數(shù)據(jù)庫中的密碼與輸入的密碼進行比較,這樣就可以確定當前用戶是否可以正常登錄
if (user.getPassword().equals(password)) { // 密碼正確

AuthenticationInfo auth = new SimpleAuthenticationInfo(user, password, "memberRealm");
return auth;
} else {
throw new IncorrectCredentialsException("密碼錯誤!");
}
}
}
}

LoginController.java

package com.highgo.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.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpSession;

@Controller
public class LoginController {

@GetMapping(value = "/login")
public String login(){
return "login";
}

@PostMapping(value = "/user_login")
public String login(@RequestParam String username, @RequestParam String password, Model model){
try {
model.addAttribute("username", username);
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, password);
Subject subject = SecurityUtils.getSubject();
//完成登錄
subject.login(usernamePasswordToken);
System.out.println("登錄成功,即將進行頁面跳轉(zhuǎn)");
return "redirect:/index";
} catch (Exception e) {
String ex = e.getClass().getName();
if (ex != null) {
if (UnknownAccountException.class.getName().equals(ex)) {
System.out.println("用戶名不存在");
} else if (IncorrectCredentialsException.class.getName().equals(ex)) {
System.out.println("賬戶或密碼錯誤");
} else {
System.out.println("未知錯誤");
}
}
//返回登錄頁面
return "login";
}
}

@ResponseBody
@GetMapping(value = "/index")
public String index(){
return "welcome";
}
}
4.4.登錄結(jié)果展示

image