Springboot与Shiro的整合

Springboot使用的页面模板是thyme leaf,它比jsp多一些特定的标签,可以动态或者静态显示文本内容

1、spring boot中导入thymeleaf页面模板

(1) 在pom.xml文件引入thymeleaf

<dependency> 
     <groupId>org.springframework.boot</groupId> 
     <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

(2)在application.properties(application.yml)文件中配置thymeleaf

thymeleaf:
        cache: false # 关闭页面缓存
        encoding: UTF-8 # 模板编码
        prefix: classpath:/templates/  # 页面映射路径
        suffix: .html # 试图后的后缀
        mode: HTML5 # 模板模式

 

(3)测试导入的thymeleaf页面

/**
     * 测试thymeleaf
     */
    @RequestMapping("/testThymeleaf")
    public String testThymeleaf(Model model){
        //把数据存入model
        model.addAttribute("name","qmh");
        //model.addAttribute默认返回的.html文件,所以这里return的是test.html
        return "/test";

    }

 

取测试页面的name名字,建立test.html页面(注意:spring boot建立html的地方为src/main/java/resources/temelates中)

<!DOCTYPE html>
<!--
  ~ /*
  ~  * Copyright (c) 2019. tdc.shangri-la.com. All Rights Reserved.
  ~  */
  -->

<br lang="en" xmlns:th="http://www.w3.org/1999/xhtml" >
<head>
    <meta charset="UTF-8">
    <title>测试thymeleaf的使用</title>
</head>
<body>
<hr>
    <!--获取name-->
    <h2 th:text="${name}"></h2>

</hr>
    
</body>
</html>

 

2.Springboot与Shiro整合实现用户认证

 2.1shiro核心ApI

* subject(包括用户登录,注销、授权的方法,需要关联securityManager)
* securityManager:安全管理器(需要连接realm)
* realm:shiro连接数据的桥梁

2.2.Springboot与Shiro整合
(1)导入shiro与spring的依赖
<!--导入shiro与spring整合的依赖-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>

(2)自定义realm类,realm类用来编写认证或授权的方式方法或者逻辑,完成shiro与其他应用的数据连接

realm类需要继承AuthorizingRealm ,实现doGetAuthorizationInfo(授权)、doGetAuthenticationInfo(认证)的方法

package com.toxic.anepoch.boot2.controller.web.Shiro;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

/**
 * Title:
 * Description:自定义realm类,实现shiro框架中realm部分的逻辑操作,
 * (1)与securitymanager部分的关联
 * (2)完成shiro与数据库的连接
 *
 * @author py
 * @date 2020/4/16 20:07.
 *
 */
public class UserRealm extends AuthorizingRealm {
    //执行授权逻辑
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("执行授权认证");
        return null;
    }
    //执行认证逻辑
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("执行逻辑认证");
        return null;
    }
}

 

 

(2)编写shiro的配置类ShiroConfigTest 

创建三个对象ShiroFilterFactoryBean、DefaultSecurityManager、realm

package com.toxic.anepoch.boot2.controller.web.Shiro;

        import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
        import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
        import org.springframework.beans.factory.annotation.Qualifier;
        import org.springframework.context.annotation.Bean;
        import org.springframework.context.annotation.Configuration;
        import java.util.LinkedHashMap;
        import java.util.Map;

/**
 * Title:
 * @author py
 * @date 2020/4/16 19:42.
 */

//@Configuration 注解,表示声明该类为Spring 的配置类
@Configuration
public class ShiroConfigTest {

// 创建三个对象

    /**
     * 创建ShiroFilterFactoryBean(关联manager)
     */
//@Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager securityManager ){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
       
        return shiroFilterFactoryBean;

    }

    /**
     *创建DefaultSecurityManager(关联realm对象)
     */
//@Bean
//@qualifier,qualifier的意思是合格者,通过这个标示,表明了哪个实现类才是我们所需要的,该注解是用来消除依赖注入冲突的
    public DefaultWebSecurityManager  getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(userRealm);
        return securityManager;
    }

    /**
     *创建realm(自定义一个realm实体类,通过实体类进行调用)
     */
    //创建一个方法,方法返回对象即我们要创建的对象 UserRealm ,返回该对象的实例。在方法上打上注解@Bean即表示声明该方法返回的实例是受 Spring 管理的 Bean,便于其他方法使用
    @Bean
    public UserRealm getUserBealm(){
        UserRealm userRealm = new UserRealm();
        return  userRealm;
    }
}

 

2.3Shiro常用的内置过滤器实现url页面的拦截
* 常用的一些过滤器等级:
* anon:无需认证(登录)就可以访问
* authc:必须认证才可以访问
* user:如果使用rememberMe的功能,可以直接访问
* perms;该资源必须授予资源权限才可以访问
* role;该资源必须授予角色权限才可以访问
package com.toxic.anepoch.boot2.controller.web.Shiro;

import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;
import java.util.Map;


//@Configuration 注解,表示声明该类为Spring 的配置类
@Configuration
public class ShiroConfigTest {

/**
 * 创建ShiroFilterFactoryBean
 */
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager securityManager ){
    ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
    //设置安全管理器等级
    shiroFilterFactoryBean.setSecurityManager(securityManager);

    //添加shiro的内置过滤器,实现url的相关的拦截
/**
 * 常用的一些过滤器等级:
 * anon:无需认证(登录)就可以访问
 * authc:必须认证才可以访问
 * user:如果使用rememberMe的功能,可以直接访问
 * perms;该资源必须授予资源权限才可以访问
 * role;该资源必须得到角色权限才可以访问
 */
//创建一个map集合,用来参访需要拦截的url路径(controller中 @RequestMapping("/"))即及改路径的过滤等级,这里创建LinkedHashMap集合,是为了存入的数据具有顺序
    Map<String,String> filterMap = new LinkedHashMap<>();
//若拦截生效后默认跳转到login.jsp
    filterMap.put("/add","authc");
    filterMap.put("/update","authc");
    /*
    要想对user下所有的url都过滤,那么可以用*  (*通配符)
    filterMap.put("/user/*","authc");
    */
    /*
    *想对某一个url做放行处理可以用以下方法
    * filterMap.put("/testThymeleaf","anon");
    * */
    //修改拦截的时侯跳转的页面(这里用的authc等级,就可以理解为要先完成登录),登录页面都从controller层进行关联,toLogin是controller的方法,用来访问login登录页面
    shiroFilterFactoryBean.setLoginUrl("/toLogin");
    shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);

    return shiroFilterFactoryBean;

}

/**
 *创建DefaultSecurityManager(关联realm对象)
 */
@Bean(value = "securityManager")
    public DefaultWebSecurityManager  getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
       DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(userRealm);
        return securityManager;
    }

    /**
     *创建realm(自定义一个realm实体类,通过实体类进行调用)
     */
        //在创建一个方法,方法返回对象即我们要创建的对象 UserRealm , 返回该对象的实例。在方法上打上注解@Bean即表示声明该方法返回的实例是受 Spring 管理的 Bean,便于其他方法使用
    @Bean(value = "userRealm")
    public UserRealm getUserBealm(){
        UserRealm userRealm = new UserRealm();
        return  userRealm;
    }
}

 

2.4Shiro用户登录操作
创建一个登陆页面:
<!DOCTYPE html>
<!--
  ~ /*
  ~  * Copyright (c) 2019. tdc.shangri-la.com. All Rights Reserved.
  ~  */
  -->

<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>用户的登录页面</title>
</head>
<body>
    <h3>登录</h3>
    <!--获取controller中用户名或是密码的错误信息-->
    <p color="red" th:text="${msg}"></p>
<form method="post" action="login">
    用户名:<input type="text" name="name"> </br>
    密码:<input type="password" name="password"></br>
    <input type="submit" value="登录">
</form>
</body>
</html>

 

 controller的登录逻辑:

//登陆的逻辑处理,接受表单中的name,password两个参数
    @RequestMapping("/login")
    public String login(String name,String password,Model model){
        //使用shiro编写认证操作
        //1.获取subject对象
        Subject subject = SecurityUtils.getSubject();
        //2.封装用户的数据,将表单中的数据传递给token对象
        UsernamePasswordToken token = new UsernamePasswordToken(name,password);

        //3.执行登录方法,这里使用try...catch来判断是否登录成功,若没有异常则登陆成功,有异常在进行判断并抛出异常
        try {
            subject.login(token);
            System.out.println("登陆成功");
           //登陆成功跳转到主页test.html (redirect:重定向)
            return "redirect:/hello/testThymeleaf";
        } catch (UnknownAccountException e) {
            //用户名不存在,信息返回到html页面
            model.addAttribute("msg","用户名不存在");
            //返回到登陆页面
            return "login";
        }
        catch (IncorrectCredentialsException e) {
            //密码错误,信息返回到html页面
            model.addAttribute("msg","密码错误");
            //返回到登陆页面
            return "login";
        }
package com.toxic.anepoch.boot2.controller.web.Shiro;
        import org.apache.shiro.authc.*;
        import org.apache.shiro.authz.AuthorizationInfo;
        import org.apache.shiro.realm.AuthorizingRealm;
        import org.apache.shiro.subject.PrincipalCollection;
/**
 * Title:
 * Description:自定义realm类,实现shiro框架中realm部分的逻辑操作,
 * (1)与securitymanager部分的关联
 * (2)完成shiro与数据库的连接
 *
 * @author py
 * @date 2020/4/16 20:07.
 *
 */
public class UserRealm extends AuthorizingRealm {
    //执行授权逻辑
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("执行授权认证");
        return null;
    }
    //执行认证逻辑
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken ) throws AuthenticationException {
        System.out.println("执行逻辑认证");

        //假设数据库的用户名和密码分别是:qiu,123456
        String name="qiu";
        String password="123456";
        //编写shiro的判断逻辑,判断用户名和密码是否正确
        //authenticationToken类型强制转换成controller层中的UsernamePasswordToken类型,用来存放name和password
        // (controller中token的作用是:封装用户的数据,将表单中的数据传递给token对象,具体操作:UsernamePasswordToken token = new UsernamePasswordToken(name,password);)
        UsernamePasswordToken  Token = (UsernamePasswordToken) authenticationToken;
        if(!Token.getUsername().equals(name)){
            System.out.println("用户名不存在");
            return null;//shiro的底层会抛出UnknownAccountException异常
        }else{
            //判断密码,直接创建AuthorizationInfo对象的子对象,传入password参数就可以判断密码是否一致
            return new SimpleAuthenticationInfo("",password,"");
        }
    }
}
2.5整合myBatis实现用户的登录

2.5.1导入myBatis的相关的依赖
<!-- mybatis-plus begin mybtis启动器-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatisplus-spring-boot-starter</artifactId>
            <version>${mybatisplus-spring-boot-starter.version}</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus</artifactId>
            <version>${mybatisplus.version}</version>
        </dependency>
        <!-- mybatis-plus end -->
        <!--sharding-jdbc start -->
        <dependency>
            <groupId>io.shardingjdbc</groupId>
            <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
            <version>${sharding-sphere.version}</version>
        </dependency>
        <!-- sharding-jdbc end -->
        <!--mysql start-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql-connector-java.version}</version>
            <scope>runtime</scope>
        </dependency>
        <!--mysql end-->
        <!-- druid 数据库连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>${com.alibaba.druid.version}</version>
        </dependency>
    </dependencies>

2.5.2创建application.properties或application.yml文件,用来设置一些参数

//数据库的配置
 datasource:
      type: com.alibaba.druid.pool.DruidDataSource
      #druid相关配置
      driver-class-name: com.mysql.cj.jdbc.Driver
      #基本属性
      url: jdbc:mysql:
      username: 
      password: 

//mybatis的配置
mybatis:
  mapper-locations: classpath:mybatis/mapper/*.xml
  type-aliases-package: com.toxic.anepoch.boot2.service.bean

2.5.3在com.toxic.anepoch.boot2.service.bean包下创建user实体类

package com.toxic.anepoch.boot2.service.bean;

/**
 * Title:
 * Description:
 *
 * @author py
 * @date 2020/4/19 22:15.
 */
public class User {
    private int id;
    private String name;
    private String password;

    public User() {
    }

    public User(int id, String name, String password) {
        this.id = id;
        this.name = name;
        this.password = password;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", password='" + password + '\'' +
                '}';
    }

2.5.4创建mapper接口实现与数据库的关联并创建mapper.xml文件

package com.toxic.anepoch.boot2.service.dao;

import com.toxic.anepoch.boot2.service.bean.User;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;

/**
 * Title:查询用户
 * Description:
 *
 * @author py
 * @date 2020/4/19 22:22.
 */
@Repository
//@Repository用在持久层的接口上,这个注解是将接口的一个实现类交给spring管理。
public interface UserMapper {
    User findByName(@Param(value = "name") String name);
}

 

<?xml version="1.0" encoding="UTF-8" ?>
<!--
  ~ /*
  ~  * Copyright (c) 2019. tdc.shangri-la.com. All Rights Reserved.
  ~  */
  -->

<!DOCTYPE mapper PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">


<!-- 不使用namespace的话sql搜索定位会比较方便 -->
<mapper namespace="com.toxic.anepoch.boot2.service.dao.UserMapper">
    <resultMap id="UserInfoResultMap" type="com.toxic.anepoch.boot2.service.bean.User">
        <result property="id" column="id"/>
        <result property="name" column="name"/>
        <result property="password" column="password"/>

    </resultMap>

    <!-- 用于select查询公用抽取的列 -->
    <sql id="UserInfoColumns">
        <![CDATA[
        id,name,password
        ]]>
    </sql>


<select id="findByName" resultMap="UserInfoResultMap">
        SELECT <include refid="UserInfoColumns" />
        <![CDATA[
            FROM user
            WHERE
                name = #{name}
        ]]>
    </select>

</mapper>

 

2.5.5编写service接口和实现类,用于调用mepper接口

package com.toxic.anepoch.boot2.service.service;

import com.toxic.anepoch.boot2.service.bean.User;

/**
 * Title:
 * Description:
 *
 * @author py
 * @date 2020/4/19 22:30.
 */
public interface UserService {
    /**
     *通过名字进行查询
     */
    User findByName(String name);
}

 

package com.toxic.anepoch.boot2.service.service.impl;

import com.toxic.anepoch.boot2.service.bean.User;
import com.toxic.anepoch.boot2.service.dao.UserMapper;
import com.toxic.anepoch.boot2.service.service.UserService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * Title:
 * Description:
 *
 * @author py
 * @date 2020/4/19 22:34.
 */
@Service
public class UserServiceImpl implements UserService{
    @Resource
    // @Resource注入mapper接口
    private UserMapper userMapper;

    @Override
    public User findByName(String name) {
        return userMapper.findByName( name);
    }
}

 

2.5.5编写Biz接口和实现类,用于调用service接口和业务的创建

package com.toxic.anepoch.boot2.biz.business;

import com.toxic.anepoch.boot2.service.bean.User;

/**
 * Title:
 * Description:
 *
 * @author py
 * @date 2020/4/19 22:58.
 */

public interface UserBiz {
    User findByName(String name);
}

 

/**
 * Title:
 * Description:
 *
 * @author py
 * @date 2020/4/19 22:59.
 */
@Component
public class UserBizImpl implements UserBiz{

    @Resource
   private UserService userService;
    @Override
    public User findByName(String name) {
        return userService.findByName(name);
    }
}

对UserRealm类进行修改

public class UserRealm extends AuthorizingRealm {
    //执行授权逻辑
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("执行授权认证");
        return null;
    }
    @Autowired
    //@Autowired注入UserBiz接口
    private UserBiz userBiz;
    //执行认证逻辑
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken ) throws AuthenticationException {
        System.out.println("执行逻辑认证");

        //假设数据库的用户名和密码分别是:qiu,123456
       // String name="qiu";
       // String password="123456";
        //编写shiro的判断逻辑,判断用户名和密码是否正确
        //authenticationToken类型强制转换成controller层中的UsernamePasswordToken类型,用来存放name和password
        // (controller中token的作用是:封装用户的数据,将表单中的数据传递给token对象,具体操作:UsernamePasswordToken token = new UsernamePasswordToken(name,password);)
        UsernamePasswordToken  Token = (UsernamePasswordToken) authenticationToken;

        User user = userBiz.findByName(Token.getUsername());
        if(user==null){
            System.out.println("用户名不存在");
            return null;//shiro的底层会抛出UnknownAccountException异常
        }else{
            //判断密码,直接创建AuthorizationInfo对象的子对象,传入password参数就可以判断密码是否一致
            return new SimpleAuthenticationInfo("",user.getPassword(),"");
        }
    }
}

 3.spring boot与shiro整合实现用户授权

3.1使用shiro的内置过滤器拦截资源

常用的授权过滤器:perms,role,授权过滤器与认证过滤器不同的是:授权过滤器需要加授权字符串,这里给add接口添加授权

 //添加授权过滤器,授权字符串,授权字符串可以任意的指定
    filterMap.put("/add","perms:[user:add]");

未授权过滤器生效后,会自动跳转到默认的未授权页面,当然也可以自定义一个未授权的页面

需要在controller层建立一个/noAuth路径去访问noAuth页面

 //设置未授权页面,授权过滤器生效,跳转到noAuth页面
    shiroFilterFactoryBean.setUnauthorizedUrl("/noAuth");

3.2编写资源授权逻辑

public class UserRealm extends AuthorizingRealm {
    //执行授权逻辑
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("执行授权认证");
        //给资源授权
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //添加getShiroFilterFactoryBean方法中设置的授权字符串
        info.addStringPermission("user:add");
        return info;
    }
}

3.3关联数据库实现动态授权

从新编写一个select方法,【包括:UserMapper.xml, UserMapper实体类(拥有id,name,password,perms),UserService接口、UserServiceImpl实现类、UserBiz接口、UserBizImpl实现类、controller层的授权测试方法】,此过程与验证时通过名字进行查询的步骤相似

UserRealm类中的授权逻辑的修改

 //执行授权逻辑
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("执行授权认证");
        //给资源授权,创建AuthorizationInfo接口的实现类:SimpleAuthorizationInfo
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

        //添加getShiroFilterFactoryBean方法中设置的授权字符串
        //info.addStringPermission("user:add");

        //到数据库查询登录用户的授权字符串
        Subject subject = SecurityUtils.getSubject();
        //获取当前登录对象
        User user =(User) subject.getPrincipal();
        User dbUser = userBiz.findById(user.getId());
        info.addStringPermission(dbUser.getPerms());

        return info;
    }

 

 

3.4thymeleaf标签与shiro进行整合使用

3.4.1导入thyme leaf扩展的坐标

<!--导入thymeleaf对shiro扩展的坐标-->
        <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>2.0.0</version>
        </dependency>

3.4.2在ShiroConfigTest配置类中添加配置ShiroDialect的方法,用于thymeleaf标签与shiro进行整合使用

 //ShiroDialect
    @Bean
    public ShiroDialect getDhiroDialect(){
       return new ShiroDialect();
    }

3.5在thymeleaf页面上使用shiro标签,实现的效果是用户登录成功后先判断此用户是否有授权权限,有相应的授权就展示出来,没有则不进行展示

<body>
<h2 color = "red" th:text="${name}"></h2>
</hr>
<div shiro:HasPermissionAttrProcessor="user:add"><!--判断用户是否有add权限update,有则可以访问该内容-->
    进入用户的添加操作;<a href="add">增加用户</a></br>
</div>
<div shiro:HasPermissionAttrProcessor="user:update">
    进入用户的更新操作:<a href="update">更新用户</a> <!--判断用户是否有权限-->
</div>
</body>

 

 

 

 

 

 

 

 

 

 



 

posted @ 2020-04-20 12:52  QMH  阅读(258)  评论(0编辑  收藏  举报