一 、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/
/**
* 添加
* @return
*/