Shiro实战+springboot集成

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
简介: Shiro实战+springboot集成


注:本文只做流程分享,重点解读,源码已上传github,源码获取方式(左下角阅读原文)

Springboot+Shiro

版本介绍

Springboot: 2.0.5.RELEASE

Java: JDK1.8

Shiro: 1.4.1

MySQL:8.0

本文简介

该例子中使用Springboot集成Shiro的方式展示Shiro 授权,主要涉及技术点,tkmapper、Druid、maven、mysql、lombok

初始化mysql 数据库:init.sql见resources/db/init.sql

引入maven依赖:见pom.xml

修改generator.xml配置文件(涉及数据库连接地址信息以及对应数据表修改)

点击maven插件(mybatis-generator)生成entity,dao,mapper

生成的代码结构如下

到此权限代码的基础就已经搭建好了,使用的用户-角色-权限模型也是经典的权限设计模型

配置application.xml

server:
  port: 9006
#数据源配置
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource #Druid连接池
    url: jdbc:mysql://localhost:3306/springboot-demo?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&useSSL=true&nullCatalogMeansCurrent=true
    username: root #数据库用户名
    password: root #数据库密码
    driver-class-name: com.mysql.cj.jdbc.Driver #mysql驱动
  thymeleaf:
    prefix: classpath:/templates/
    suffix: .html
mybatis:
  mapper-locations: classpath:/mapper/*.xml
  configuration:
    map-underscore-to-camel-case: true
    #打印sql
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper:
  identity: MYSQL # 配置主键自动增长(使用MYSQL原生方式)
logging:
  level:
    com.tz.springbootshiro: info
    
# 分页插件
pagehelper:
  reasonable: true
  page-size-zero: true
  params: pageNum=start;pageSize=limit
  support-methods-arguments: true

配置shiroconfig类,配置我们需要的组件

package com.tz.springbootshiro.config;
import com.oracle.tools.packager.Log;
import com.tz.springbootshiro.realm.MyRealm;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authc.pam.AuthenticationStrategy;
import org.apache.shiro.authc.pam.FirstSuccessfulStrategy;
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.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.apache.shiro.mgt.SecurityManager;
import java.util.LinkedHashMap;
import java.util.Map;
/**
 * @author tz
 * @Classname ShiroConfig
 * @Description shiro 配置
 * @Date 2019-11-10 09:23
 */
@SpringBootConfiguration
public class ShiroConfig {
    /**
     * 凭证匹配器
     * (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
     * 所以我们需要修改下doGetAuthenticationInfo中的代码;
     * )
     *
     * @return
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        //散列算法:这里使用MD5算法;
        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        //散列的次数,比如散列两次,相当于 md5(md5(""));
        hashedCredentialsMatcher.setHashIterations(2);
        // 是否存储为16进制
        hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);
        return hashedCredentialsMatcher;
    }
    /**
     * Shiro默认提供了三种 AuthenticationStrategy 实现:
     * AtLeastOneSuccessfulStrategy :其中一个通过则成功。
     * FirstSuccessfulStrategy :其中一个通过则成功,但只返回第一个通过的Realm提供的验证信息。
     * AllSuccessfulStrategy :凡是配置到应用中的Realm都必须全部通过。
     * authenticationStrategy
     *
     * @return
     */
    @Bean(name = "authenticationStrategy")
    public AuthenticationStrategy authenticationStrategy() {
        Log.info("----------->>>>>>>>>>>>ShiroConfiguration.authenticationStrategy()");
        return new FirstSuccessfulStrategy();
    }
    /**
     * 安全管理器,并把realm注入
     * @return
     */
    @Bean
    public DefaultWebSecurityManager securityManager(){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myRealm());
        return securityManager;
    }
    /**
     * 开启shiro aop注解支持.
     * 使用代理方式;所以需要开启代码支持;
     *  @RequiresPermissions("topic:list")
     *  @RequiresPermissions
     * @RequiresRoles
     * @RequiresUser
     * @RequiresGuest
     * @RequiresAuthentication
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
    @Bean
    @ConditionalOnMissingBean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return defaultAdvisorAutoProxyCreator;
    }
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        shiroFilterFactoryBean.setLoginUrl("/login");
        shiroFilterFactoryBean.setSuccessUrl("/");
        shiroFilterFactoryBean.setUnauthorizedUrl("/unauth");
        //注意此处使用的是LinkedHashMap,是有顺序的,shiro会按从上到下的顺序匹配验证,匹配了就不再继续验证
        //所以上面的url要苛刻,宽松的url要放在下面,尤其是"/**"要放到最下面,如果放前面的话其后的验证规则就没作用了。
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        filterChainDefinitionMap.put("/static/**/**", "anon");
        filterChainDefinitionMap.put("/login", "anon");
        filterChainDefinitionMap.put("/captcha.jpg", "anon");
        filterChainDefinitionMap.put("/favicon.ico", "anon");
        filterChainDefinitionMap.put("/**", "authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }
    /**
     * 自定义realm
     */
    @Bean
    public MyRealm myRealm(){
        MyRealm userRealm = new MyRealm();
        userRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return userRealm;
    }
}

自定义Realm 授权验证使用代码

package com.tz.springbootshiro.realm;
import com.tz.springbootshiro.bean.SysUser;
import com.tz.springbootshiro.service.SysPermissionService;
import com.tz.springbootshiro.service.SysUserService;
import lombok.extern.slf4j.Slf4j;
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.realm.Realm;
import org.apache.shiro.realm.text.TextConfigurationRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import java.util.List;
/**
 * @author tz
 * @Classname MyRealm
 * @Description
 * @Date 2019-11-09 11:44
 */
@Slf4j
public class MyRealm extends AuthorizingRealm {
    @Autowired
    private SysUserService sysUserService;
    @Autowired
    private SysPermissionService sysPermissionService;
    /**
     * 授权
     *
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        SysUser sysUser = (SysUser) principals.getPrimaryPrincipal();
        List<String> sysPermissions = sysPermissionService.selectPermissionByUserId(sysUser.getUserId());
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addStringPermissions(sysPermissions);
        log.info("------------>>>>>>>>>>>>>>>>>>>>>>>>>>>>doGetAuthorizationInfo");
        return info;
    }
    /**
     * 认证
     *
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        SysUser sysUser = sysUserService.findByUserName(token.getUsername());
        if (sysUser == null) {
            return null;
        }
        log.info("------------>>>>>>>>>>>>>>>>>>>>>>>>>>>>doGetAuthenticationInfo");
        return new SimpleAuthenticationInfo(sysUser, sysUser.getPassword().toCharArray(), ByteSource.Util.bytes(sysUser.getSalt()), getName());
    }
    /**
     * 测试realm
     *
     * @return
     */
    @Bean
    public Realm realm() {
        TextConfigurationRealm realm = new TextConfigurationRealm();
        realm.setUserDefinitions("joe.coder=password,user\n" +
                "jill.coder=password,admin");
        realm.setRoleDefinitions("admin=read,write\n" +
                "user=read");
        realm.setCachingEnabled(true);
        return realm;
    }
}
  1. 到此基础配置就结束了,对应的数据库实体类相关关系类可在github查找,下面掩饰结果,首先分析拦截url
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        shiroFilterFactoryBean.setLoginUrl("/login");
        shiroFilterFactoryBean.setSuccessUrl("/");
        shiroFilterFactoryBean.setUnauthorizedUrl("/unauth");
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        filterChainDefinitionMap.put("/static/**/**", "anon");
        filterChainDefinitionMap.put("/login", "anon");
        filterChainDefinitionMap.put("/captcha.jpg", "anon");
        filterChainDefinitionMap.put("/favicon.ico", "anon");
        filterChainDefinitionMap.put("/**", "authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

上方代码,我们在shiroconfig配置中指定的,登录url为/login,此处为get请求,找到controller中我们的/login的地址进行跳转,此处需要注意的是我们的controller不能返回json体,即不能加入RequestBody注解,登录成功的url为/,此处没有该url所以代码不会跳转,我们实际项目中可以把此改为我们登录成功之后跳转的url,setUnauthorizedUrl配置我们未授权页面的url,然后下面配置我门不需要拦截的url,最后加上/**的拦截验证,url依次往下,注意顺序,这个拦截全部一定要放在最后,不然其他配置就不生效,下面放上我controller的代码

package com.tz.springbootshiro.controller;
import com.tz.springbootshiro.common.CodeMsg;
import com.tz.springbootshiro.common.ResultBean;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.subject.Subject;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
/**
 * @author tz
 * @Classname LoginController
 * @Description 登录
 * @Date 2019-11-10 09:52
 */
@Controller
public class LoginController {
    /**
     * shiro 跳转登录页面使用
     * @return
     */
    @RequestMapping(value = "/login", method = RequestMethod.GET)
    public String defaultLogin() {
        return "/login/login";
    }
    /**
     * 登录页面表单提交使用
     * @param username
     * @param password
     * @return
     */
    @ResponseBody
    @PostMapping(value = "/login")
    public ResultBean login(@RequestParam("userName") String username, @RequestParam("password") String password) {
        // 从SecurityUtils里边创建一个 subject
        Subject subject = SecurityUtils.getSubject();
        // 在认证提交前准备 token(令牌)
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        // 执行认证登陆
        try {
            subject.login(token);
        } catch (UnknownAccountException uae) {
            return new ResultBean(CodeMsg.ACCOUNT_NOT_FOUND);
        } catch (IncorrectCredentialsException ice) {
            return new ResultBean(CodeMsg.PASSWORD_ERROR);
        } catch (LockedAccountException lae) {
            return new ResultBean(CodeMsg.ACCOUNT_LOCK);
        } catch (ExcessiveAttemptsException eae) {
            return new ResultBean(CodeMsg.ERROR_NUM_MORE);
        } catch (AuthenticationException ae) {
            return new ResultBean(CodeMsg.USERNAME_PASSWORD_ERROR);
        }
        if (subject.isAuthenticated()) {
            return new ResultBean(CodeMsg.LOGIN_SUCCESS);
        } else {
            token.clear();
            return new ResultBean(CodeMsg.LOGIN_ERROR);
        }
    }
}

ok,教程到此结束,纯干货,源码----->阅读原文

相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
19天前
|
消息中间件 Java Kafka
Springboot集成高低版本kafka
Springboot集成高低版本kafka
|
25天前
|
NoSQL Java Redis
SpringBoot集成Redis解决表单重复提交接口幂等(亲测可用)
SpringBoot集成Redis解决表单重复提交接口幂等(亲测可用)
284 0
|
30天前
|
NoSQL Java Redis
SpringBoot集成Redis
SpringBoot集成Redis
426 0
|
22小时前
|
Java Spring
Spring Boot脚手架集成校验框架
Spring Boot脚手架集成校验框架
6 0
|
2天前
|
Java Docker 容器
SpringBoot项目集成XXL-job
SpringBoot项目集成XXL-job
|
4天前
|
Java 关系型数据库 数据库
【SpringBoot系列】微服务集成Flyway
【4月更文挑战第7天】SpringBoot微服务集成Flyway
【SpringBoot系列】微服务集成Flyway
|
19天前
|
SQL Java 调度
SpringBoot集成quartz定时任务trigger_state状态ERROR解决办法
SpringBoot集成quartz定时任务trigger_state状态ERROR解决办法
|
26天前
|
NoSQL Java Redis
SpringBoot集成Redis
SpringBoot集成Redis
54 1
|
29天前
|
Java 测试技术 Maven
SpringBoot集成Elasticsearch
SpringBoot集成Elasticsearch
24 0
|
JSON 前端开发 Java
SpringBoot 实战:一招实现结果的优雅响应
今天说一下 Spring Boot 如何实现优雅的数据响应:统一的结果响应格式、简单的数据封装。
313 0
SpringBoot 实战:一招实现结果的优雅响应