springboot整合shiro

走远了吗. 提交于 2020-01-25 18:08:20

前言

springboot的配置和ssm的配置有所区别,但是它们本质上又是一样的。shiro的整合也是如此,虽然配置方式不一样,一个人xml中一个在java代码中,但是他们的流程还是差不多的,都是先配置凭证器->自定义realm->安全管理器->过滤器链等。所以在这篇博客中,我将参考前两天写的ssm整合shiro来进行springboot整合shiro的配置。

springboot基本配置 ----- 整合mybatis

pom.xml如下

<?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 https://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.2.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.qiu</groupId>
    <artifactId>shirobyboot</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>shirobyboot</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.1</version>
        </dependency>
        <!-- druid数据库连接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!--        shiro-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.1</version>
        </dependency>
        <!-- shiro-thymeleaf 让shiro的标签能在thymeleaf中使用-->
        <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>2.0.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
	<!-- 让java文件下的xml文件能够编译进target文件中,程序运行过程中能够扫描到这里-->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <filtering>true</filtering>
            </resource>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
            </resource>
        </resources>
    </build>
</project>

文件目录和ssm整合shiro篇一致,截图如下,如有疑惑的伙伴可以点进超链接,在那篇文章中我已经略做说明了。
文件目录截图如下
在这里插入图片描述
application.yml配置如下

spring:
  datasource:
    druid:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/shiro_ssm?useUnicode=true&characterEncoding=utf8&autoReconnect=true&serverTimezone=GMT%2B8&useSSL=false
      username: root
      password: root
      max-active: 20
      max-wait: 6000
      initial-size: 1
 #禁用thymeleaf的缓存
  thymeleaf:
    cache: false
 #定义mapper.xml文件的路径
mybatis:
  mapper-locations: classpath:com/qiu/shirobyboot/mapper/xml/*.xml

springboot不像ssm可以自动创建出mapper接口的代理对象供其他bean注入,在springboot中想要创建出mapper接口的代理对象需要扫描,只需要如下配置
在这里插入图片描述

package com.qiu.shirobyboot;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.qiu.shirobyboot.mapper")
public class ShirobybootApplication {

    public static void main(String[] args) {
        SpringApplication.run(ShirobybootApplication.class, args);
    }

}

当然如果不想配置这个@MapperScan,可以在每一个mapper接口上添加一个@Mapper,但是明显很麻烦。
在这里插入图片描述

package com.qiu.shirobyboot.mapper;


import com.qiu.shirobyboot.pojo.Permission;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

/**
 * @Author VULCAN
 * @create 2020/1/17 20:34
 */
@Mapper
public interface PerMapper {
    List<Permission> selPerByRole(@Param("roleid")Integer roleid);
}

在自动类上加@MapperScan和在mapper接口上加@Mapper都是同样的效果,伙伴们可以选择一种方式去创建mapper接口的代理对象,当然两种方式并存也没什么关系。

需要一提的是,在service实现层注入mapper接口的时候,会出现爆红现象,纯属正常不影响运行。
在这里插入图片描述
以上springboot整合mybatis基本整合完毕,开始测试。
mapper.xml部分sql如下

	<select id="selRole" resultType="com.qiu.shirobyboot.pojo.Role">
        select * from role
    </select>

controller层接口代码如下

	@RequestMapping("/test2")
    public List<Role> test2(){
        return roleService.selRole();
    }

运行结果如下
在这里插入图片描述

开始shiro的整合

自定义realm的创建

其实和ssm整合shiro的自定义realm一样(我直接拷贝过来的)代码如下
MyRealm.java

package com.qiu.shirobyboot.config;

import com.qiu.shirobyboot.pojo.Permission;
import com.qiu.shirobyboot.pojo.Role;
import com.qiu.shirobyboot.pojo.User;
import com.qiu.shirobyboot.service.PerService;
import com.qiu.shirobyboot.service.RoleService;
import com.qiu.shirobyboot.service.UserService;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
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.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * @Author VULCAN
 * @create 2020/1/19 17:10
 */
//MyRealm在配置文件中已经注册成bean了,所以可以在它的类下使用@Autowired
public class MyRealm extends AuthorizingRealm {

    @Autowired
    UserService userService;
    @Autowired
    RoleService roleService;
    @Autowired
    PerService perService;
    @Override
    public String getName(){
        return this.getClass().getSimpleName();
    }
    //角色权限添加
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        User user = (User)principalCollection.getPrimaryPrincipal();
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        List<Role> roles = roleService.selRoleByUser(user.getUserid());
        Collection<String> roleNames=new ArrayList<String>();
        Collection<String> perCodes=new ArrayList<String>();
        if(roles!=null && roles.size()>0){
            for (Role role:roles){
                String rolename = role.getRolename();
                int roleid = role.getRoleid();
                roleNames.add(rolename);
                //获得所有权限功能
                List<Permission> permissions = perService.selPerByRole(roleid);
                if(permissions!=null && permissions.size()>0){
                    for (Permission permission:permissions){
                        String percode = permission.getPercode();
                        perCodes.add(percode);
                    }
                }
            }
            //增加角色
            info.addRoles(roleNames);
            //增加权限功能
            info.addStringPermissions(perCodes);
        }
        return info;
    }
    //信息验证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken info) throws AuthenticationException {
        User users = userService.selUser(info.getPrincipal().toString());
        if(users!=null){
            String password = users.getPassword();
            String username = users.getUsername();
            String solt = users.getSolt();
            ByteSource soltToByte=ByteSource.Util.bytes(solt);
            //如果与前台输入的接口不匹配,则密码错误,抛出密码不匹配异常
            SimpleAuthenticationInfo logininfo=new SimpleAuthenticationInfo(users,password,soltToByte,this.getName());
            return logininfo;
        }else{
            //返回空就表示用户名不存在
            return null;
        }

    }
}

shiro安全管理器、过滤器链等配置

package com.qiu.shirobyboot.config;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

/**
 * @Author VULCAN
 * @create 2020/1/23 17:46
 */
@Configuration
public class ShiroConfig {
	//匿名登录————指不需要登录(身份验证)就可以访问的路径
	//匿名登录路径的数组,为后续过滤器链所提供
    private String[] anonArr={"/startlogin","/login"};
    //配置凭证匹配器MD5
    @Bean
    HashedCredentialsMatcher hashedCredentialsMatcher(){
        HashedCredentialsMatcher hashedCredentialsMatcher=new HashedCredentialsMatcher();
        //设置加密算法为md5
        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        //加密迭代次数为一次
        hashedCredentialsMatcher.setHashIterations(1);
        return hashedCredentialsMatcher;
    }
    //配置自己的realm
    @Bean
    MyRealm myRealm(){
        MyRealm realm=new MyRealm();
        //给自定义realm注入凭证匹配器
        realm.setCredentialsMatcher(hashedCredentialsMatcher());
        return realm;
    }

    //配置SecurityManager
    @Bean
    DefaultWebSecurityManager defaultSecurityManager(){
        DefaultWebSecurityManager defaultSecurityManager = new DefaultWebSecurityManager();
        //将自己的realm注入到安全管理器中
        defaultSecurityManager.setRealm(myRealm());
        return defaultSecurityManager;
    }
    //配置过滤器链
    @Bean
    ShiroFilterFactoryBean shiroFilterFactoryBean(){
        ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();
        //key是路径,value是访问模式,给过滤器注入用
        Map<String,String> map=new HashMap<>();
        for (String anno:anonArr) {
            map.put(anno,"anon");
        }
        map.put("/logout","logout");
        map.put("/**","authc");
        //将安全管理器配置进过滤器中,让过滤器生效
        shiroFilterFactoryBean.setSecurityManager(defaultSecurityManager());
        shiroFilterFactoryBean.setLoginUrl("/startlogin");
        //配置过滤器链------路径访问模式
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
        return shiroFilterFactoryBean;
    }
    //让thymeleaf整合shiro的标签生效,在ssm里面,因为shiro自己已经提供了标签库,所以不用多余的配置,只需要在指定页面引入一下标签库就可以生效
    @Bean
    public ShiroDialect shiroDialect(){
        return new ShiroDialect();
    }
    //让shiro注解生效↓
    @Bean
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }
    @Bean
    AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(){
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor= new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(defaultSecurityManager());
        return authorizationAttributeSourceAdvisor;
    }
    //让shiro注解生效↑
//    我没有扒过源码,所以不知道为什么springboot整合shiro的时候不需要配置shiro过滤器,但是ssm还是需要的
}

由上述配置得知,/startlogin为访问login页的页面接口,当系统发现某个用户没有验证信息(未登录)将会强制跳转到/startlogin接口,登录且将用户信息保存进session的接口是/login,登出是/logout,除以上这些路径以外,其余的路径都需要身份认证。

controller层
仍旧和ssm整合shiro里面的controller一样

package com.qiu.shirobyboot.controller;

import com.qiu.shirobyboot.pojo.User;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpSession;

/**
 * @Author VULCAN
 * @create 2020/1/19 21:01
 */
@Controller
public class PageControlle {
    //登录页,没有得到授权的页面都会跳到这个页面
    @RequestMapping("startlogin")
    public String runStartlogin(){
        return "login";
    }
    //内容页,不过因为配置了过滤器,只有登录成功后才能访问
    @RequestMapping("toContent")
    public String toContent(){
        return "content";
    }

    @RequestMapping("login")
    public String login(String username , String password ,boolean rememberme, HttpSession httpSession){
        //不需要绑定线程
        //获得主体对象
        Subject subject = SecurityUtils.getSubject();
        //得到token信息
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        //记住我功能
        token.setRememberMe(rememberme);
        //开始登录,进入myrealm中,如果返回为null则代表账户不存在,会抛出异常。匹配不上,会抛出异常
        try {
            subject.login(token);
            User userinfo = (User)subject.getPrincipal();
            httpSession.setAttribute("userinfo",userinfo);
           //登录正确跳转到toContent的路径映射
            return "redirect:toContent";
        } catch (AuthenticationException e) { //CredentialsException密码与账户不匹配   //AccountException账户错误(不存在)
            System.out.println("用户名或密码有错误");
            httpSession.setAttribute("error","登录错误");
            //登录错误跳转到startlogin的路径映射
            return "redirect:startlogin";
        }
    }
}

login.html
thymeleaf模板的后缀仍然是.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
我是登录页
<form action="/login" method="post">
    <input name="username" type="text" placeholder="请输入用户名">
    <input name="password" type="password" placeholder="请输入密码">
    <input type="submit" value="登录">
</form>
</body>
</html>

content.html

<!DOCTYPE html>
<html lang="en"  xmlns:th="http://www.thymeleaf.org"
                 xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<!--thymeleaf的el表达式如下,如果是session,好像需要强调是哪个作用域-->
我是内容<span th:text="${session.userinfo.username}"></span>
<!--访问的用户有user:sel权限就展现出来,没有就不显示。-->
<shiro:hasPermission name="user:sel">
    <a>用户查询</a>
</shiro:hasPermission>
<shiro:hasPermission name="user:add">
    <a>用户添加</a>
</shiro:hasPermission>
<shiro:hasPermission name="user:up">
    <a>用户修改</a>
</shiro:hasPermission>
<shiro:hasPermission name="user:del">
    <a>用户删除</a>
</shiro:hasPermission>
<shiro:hasPermission name="user:export">
    <a>导出用户</a>
</shiro:hasPermission>
</body>
</html>

要注意,想要让shiro标签生效,需要在ShiroConfig.java代码中添加如下代码

 //让thymeleaf整合shiro的标签生效,在ssm里面,因为shiro自己已经提供了标签库,所以不用多余的配置,只需要在指定页面引入一下标签库就可以生效
    @Bean
    public ShiroDialect shiroDialect(){
        return new ShiroDialect();
    }

运行服务后页面如下
在这里插入图片描述
正确输入密码后跳转到/toContent接口
结果如下,很明显,qiu这个用户只有用户查询、用户添加、用户修改这三个功能
在这里插入图片描述

shiro相关注解起效说明

因为这次springboot整合shiro的时候我直接把注解失效问题解决了,所以以下仅做说明
代码如下,只有user:export权限的用户才能访问这个接口
由上得知,qiu这个用户没有user:export这个权限(用户导出),按理说是不能访问的

	@RequiresPermissions("user:export")
    @RequestMapping("/test")
    public String test(){
        return "test";
    }

但是不做任何配置的话,qiu这个用户照样能访问到/test这个接口
所以相关配置如下
在ShiroConfig.java文件中添加以下代码

//让shiro注解生效↓
    @Bean
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }
    @Bean
    AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(){
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor= new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(defaultSecurityManager());
        return authorizationAttributeSourceAdvisor;
    }
    //让shiro注解生效↑

配置上述代码后,当用户强行访问没有权限的接口时,浏览器会跳转到500错误界面,所以为了美观,我们需要全局报错监控,当用户访问没有权限的接口时,返回一个字符串"noPower"
代码如下
MyExceptionHandle.java

package com.qiu.shirobyboot.ExceptionHandle;

import org.apache.shiro.authz.UnauthorizedException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * @Author VULCAN
 * @create 2020/1/22 15:27
 */

//全局异常监控,出现的异常就跑到这里来,但是需要在springmvc.xml中配置扫描到该注解
@ControllerAdvice
public class MyExceptionHandle {
    @ResponseBody
    @ExceptionHandler(value={UnauthorizedException.class})
    public String dealUnauthorized() {
        return "noPower";
    }
 }
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!