一 、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/