Springboot+Shiro+Mybatis+mysql

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

 

posted @ 2019-07-12 16:34  yiwanbin  阅读(1583)  评论(0编辑  收藏  举报