Shiro安全框架

1.简介

  • Apache Shiro 是一个功能强大且易于使用的 Java 安全(权限)框架。
  • Shiro 可以完成:认证、授权、加密、会话管理、与 Web 集成、缓存等。借助 Shiro 您可以快速轻松地保护任何应用程序,从最小的移动应用程序到最大的 Web 和企业应用程序。

2.使用Shiro的好处

  • 易于使用:Shiro构建系统安全框架非常简单,易于上手掌握。
  • 全面:Shiro 包含系统安全框架需要的功能,满足安全需求的“一站式服务”。
  • 适应灵活:在任何程序都可以应用,也没有强制要求任何规范,甚至没有很多依赖项。
  • 兼容性强:Shiro 的设计模式使其易于与其他框架和应用程序集成。Shiro 与 Spring、Grails、Wicket、Tapestry、Mule、Apache Camel、Vaadin 等框架无缝集成。
  • Shiro 是 Apache 软件基金会的一个开源项目,有完备的社区支持,文档支持。

3.三大核心

Subject

应用代码直接交互的对象是 Subject,是Shiro 的对外 API 核心,代表“当前操作的用户”,这个用户不仅仅指的是人,也可以是其他交互的事物。

SecurityManager

安全管理器;即所有与安全有关的操作都会与 SecurityManager交互;且其管理着所有 Subject、并负责进 行认证、授权、会话及缓存的管理 ;可以看出它是 Shiro 的核心。

Realm

Shiro 从 Realm 获取安全数据(如用户、角色、权限),就是说SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色/ 权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource。

4.Shiro 应用实例

4.1.开发组件
名称 版本
HighGoDB 安全版V4.5、企业版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.工程结构示例

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注解权限会失效-->
<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>
<!--数据源相关-->
<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:
# 初始化连接数量
initial-size: 5
# 最小线连接数量
min-idle: 5
# 最大连接数量
max-active: 20
# 获取连接时最大等待时间,单位毫秒
max-wait: 60000
#销毁线程时检测当前连接的最后活动时间和当前时间差大于该值时,关闭当前连接
min-evictable-idle-time-millis: 30000
#用来检测连接是否有效的sql 必须是一个查询语句
validation-query: select 1
#申请连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true
test-on-borrow: false
#归还连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为tru
test-on-return: false
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
# 配置监拉统计挡成的filters. stat: 监控统计、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;
}

//权限管理,配置主要是Realm的管理认证
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myShiroRealm());
return securityManager;
}

//Filter工厂,设置对应的过滤条件和跳转条件
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

// 必须设置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);

// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/login");
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/index");

// 权限控制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;

//实现AuthorizingRealm接口用户用户认证
public class MyShiroRealm extends AuthorizingRealm {

//角色权限和对应权限添加
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//添加角色和权限
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
System.out.println(principalCollection.getPrimaryPrincipal());
TUser user= (TUser) principalCollection.getPrimaryPrincipal();
//保存所有角色名
Set<String> allRoles = new HashSet<>();
//保存所有权限名
Set<String> allPermissions = new HashSet<>();
//查询对应角色
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());

//查询所有权限
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、登录认证的方法需要先执行,需要用他来判断登录的用户信息是否合法
String username = (String) token.getPrincipal(); // 取得用户名
// 需要通过用户名取得用户的完整信息,利用业务层操作
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());
// 将数据库中的密码与输入的密码进行比较,这样就可以确定当前用户是否可以正常登录
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("登录成功,即将进行页面跳转");
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.登录结果展示

image