Shiro与SpringBoot整合,实现登录拦截、用户认证、用户授权等。实战demo

开篇必读:

shiro这个框架虽然学习难度不高,但是底层封装很深,一篇博客也讲不明白,这东西估计得再写几篇文章,这篇文章主要是搭建一下shiro的项目架构,有shiro的基础功能。后期要添加的东西也不少,以这篇博客搭建的项目为基础,要想学shiro得一段时间,不是说你懂了就是你会了,你得动手写代码。键盘敲不烂,薪资不过万。后面继续更新,全部看完咱们不敢保证你一定会学会shiro,但一定会有收获。
坚持!

一、开发环境

名称 版本
IntelliJ IDEA 2019.3.5 x64
JDK 1.8
MySQL mysql-5.7.31-winx64
SpringBoot 2.0+
Maven apache-maven-3.6.3
shiro 1 .4.0
MyBatis 2.1.0

二、项目搭建

1、创建一个SpringBoot项目导入相关pom依赖:

   <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.2.RELEASE</version>
        <relativePath/><!-- lookup parent from repository -->
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.1.3.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <version>2.1.3.RELEASE</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <version>2.1.3.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <!--连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.16</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.0</version>
        </dependency>
        <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>2.0.0</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <fork>true</fork>
                    <addResources>true</addResources>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

2、在 resources 中创建 application.yml 配置文件。
详细配置如下:
如果你的idea是老版本可能因为系统找不到MyBatis的mapper目录而报错,你可以在resource 目录下创建mapper文件夹,也可以将 yml 配置文件中与MyBatis相关的配置暂时关掉 (注释掉的意思,配置文件中注释使用#号)。

server:
  port: 8080
spring:
  application:
    name: shiro-springboot
  datasource:
    url: jdbc:mysql://localhost:3306/hibernate
    username: root
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver
    type: com.alibaba.druid.pool.xa.DruidXADataSource
  thymeleaf:
    cache: false
  mvc:
    static-path-pattern: /templates/user/**
mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.zkr.mingyu.entity
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
logging:
  level:
    com.example.demo.dao : debug

SpringBoot开启热部署方法如下:见我另一篇博客介绍。
飞机票:SpringBoot 项目如何开启热部署

3、创建 SpringBoot 启动器 Application.class:
在这里插入图片描述
启动类源码:

package com.zkr.mingyu;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

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

}

测试项目是否可以正常启动,访问 localhost:8080 端口,查看是否运行正常。
在这里插入图片描述

三、配置 shiro 配置类

目前的项目目录:
在这里插入图片描述

编写配置类与 SpringBoot 进行整合。
配置类内容具体如下:
从底层往上描述:
如果页面中不使用shiro标签,也可以不写Shiro与Thymeleaf整合配置类。

  1. 自定义Realm。
  2. 创建 安全管理器 DefaultWebSecuityManager。
  3. 创建 ShiroFilterFactoryBean
  4. Shiro与Thymeleaf整合配置类。为了页面支持shiro标签)。

全文最重要最关键的两个点,一个是自定义 Realm 以及shiro 的配置类了。全文核心

1、自定义 Realm 类。

(1)创建 MyRealm 类,继承 AuthorizingRealm 类,必须继承。
(2)实现 doGetAuthorizationInfo、doGetAuthenticationInfo 方法。
要实现登录,doGetAuthenticationInfo 是核心,账号密码在doGetAuthenticationInfo() 方法中做校验。
而授权操作则在 doGetAuthorizationInfo() 方法中操作,比如页面资源限制,用户可以访问哪个资源,执行哪些操作等。都在这个方法中配置。

package com.zkr.mingyu.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;

import java.io.Serializable;

public class MyRealm extends AuthorizingRealm implements Serializable {

    /**
     * 授权
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("执行授权方法: doGetAuthorizationInfo");
        return null;
    }

    /**
     * 认证
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("执行认证方法: doGetAuthenticationInfo");
        return null;
    }
}

2、创建shiro配置类。

这个配置类很重要,仔细读一下,一环扣一环。后期密码加密(MD5 + salt + 散列)都在这个类中配置,以及与 Thymeleaf 整合配置等等。

package com.zkr.mingyu.config;

import com.zkr.mingyu.shiro.MyRealm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**标注 @Configuration 注解,
 * 标注这是一个配置类,
 * 让项目启动时加载该配置类。
 */
@Configuration
public class ShiroConfig {

    /**
     * 创建ShiroFilterFactory
     * 设置权限规则 需要注入securityManage
     * @param securityManager
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        return shiroFilterFactoryBean;
    }

    /**
     * 创建安全管理器,
     * 并为 securityManager 注入自定义的 Realm 类
     * @param realm
     * @return
     */
    @Bean
    public DefaultWebSecurityManager getSecurityManager(MyRealm realm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(realm);
        return securityManager;
    }

    /**
     * 配置自定义 Realm 类
     * @Bean 将 MyRealm 注入到 Spring 容器当中
     * @return
     */
    @Bean
    public MyRealm getMyRealm(){
        return new MyRealm();
    }

}

四、创建页面、Controller、 Service等。

1、在 resource 目录下创建 templeates 文件夹,用来存放 html 页面。并创建index、 login 页面。
index:
站内主页
login:
登录页面

2、Controller、 Service、 dao 等
现在的目录结构:
在这里插入图片描述
Controller:

package com.zkr.mingyu.controller;

import com.zkr.mingyu.entity.User;
import com.zkr.mingyu.service.UserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.annotation.Resource;

@Controller
public class UserController {

    @Resource
    private UserService userService;

    @RequestMapping("/index")
    public String login(User user, Model model) {
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword());

        try {
            subject.login(token);
            return "index";
        } catch (UnknownAccountException e) {
            model.addAttribute("msg", "用户名错误!");
            return "login";
        } catch (IncorrectCredentialsException e) {
            model.addAttribute("msg", "密码错误!");
            return "login";
        }

    }

    @RequestMapping("/login")
    public String index() {
        return "login";
    }

    @RequestMapping("/loginOut")
    public String loginOut() {
        Subject subject = SecurityUtils.getSubject();
        subject.logout();
        return "login";
    }

    @RequestMapping("/update")
    public String update() {
        return "/user/update";
    }

}

Service:

package com.zkr.mingyu.service;

import com.zkr.mingyu.entity.User;
import org.apache.ibatis.annotations.Param;

public interface UserService {

    /**
     * 根据用户名查找用户
     * @param userName
     * @return
     */
    User findByUserName(@Param("username") String userName);

}

ServiceImpl:

package com.zkr.mingyu.service;

import com.zkr.mingyu.dao.UserMapper;
import com.zkr.mingyu.entity.User;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;

@Service
@Transactional //开启事务
public class UserServiceImpl implements UserService {

    @Resource
    private UserMapper userMapper;

    @Override
    public User findByUserName(String userName) {
        return userMapper.findByUserName(userName);
    }
}

Dao:

package com.zkr.mingyu.dao;

import com.zkr.mingyu.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

@Mapper
public interface UserMapper {

    /**
     * 根据用户名查找用户
     * @param userName
     * @return
     */
    User findByUserName(@Param("username") String userName);

}

entity:

package com.zkr.mingyu.entity;

import java.io.Serializable;

public class User implements Serializable {

    /**
     * id
     */
    private Integer id;
    /**
     * 账号
     */
    private String username;
    /**
     * 密码
     */
    private String password;
    /**
     * 权限
     */
    private String auth;
    /**
     * 随机盐
     */
    private String salt;

    public User() {
    }

    public User(Integer id, String username, String password, String auth, String salt) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.auth = auth;
        this.salt = salt;
    }

    public Integer getId() {
        return id;
    }

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

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

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

    public String getAuth() {
        return auth;
    }

    public void setAuth(String auth) {
        this.auth = auth;
    }

    public String getSalt() {
        return salt;
    }

    public void setSalt(String salt) {
        this.salt = salt;
    }

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

Mapper:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zkr.mingyu.dao.UserMapper">
    <select id="findByUserName" parameterType="String" resultType="user">
        select id, username, password from user where username = #{username}
    </select>
</mapper>

五、修改自定义 MyRealm

自定义 MyRealm 类中 doGetAuthenticationInfo()方法主要配置用户认证。
doGetAuthorizationInfo()方法主要配置用户权限配置,说白了也就是给这个用户赋予什么角色、都有哪些权限。以及对某些资源是否可以访问,如果可以访问,那么该用户可以对这个资源执行哪些操作。操作 == CRUD呗。

package com.zkr.mingyu.shiro;

import com.zkr.mingyu.entity.User;
import com.zkr.mingyu.service.UserService;
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.springframework.beans.factory.annotation.Autowired;

import java.io.Serializable;

public class MyRealm extends AuthorizingRealm implements Serializable {

    @Autowired
    private UserService userService;

    /**
     * 执行授权逻辑
     * 权限要和资源对应
     * 权限声明该用户可以访问系统中哪些资源,对系统中哪些资源进行操作
     * 不同的用户,拥有不同的权限
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("执行授权方法: doGetAuthorizationInfo");
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        User user = (User) principals.getPrimaryPrincipal();
        User byUserName = userService.findByUserName(user.getUsername());
        /**
         * 这个方法中为授权操作
         * 基本常用方法有:
         */

        /**
         * 控制台打印结果:
         * getRoles: null
         * getObjectPermissions: null
         * getStringPermissions: [user:add]
         * getClass: class org.apache.shiro.authz.SimpleAuthorizationInfo
         */

        //获取用户角色
        /* System.out.println("getRoles: " + info.getRoles());
        System.out.println("getObjectPermissions: " + info.getObjectPermissions());
        //获取用户权限
        System.out.println("getStringPermissions: " + info.getStringPermissions());
        System.out.println("getClass: " + info.getClass());*/
        return null;
    }

    /**
     * 认证
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("执行认证方法: doGetAuthenticationInfo");
        String username = (String) token.getPrincipal();
        User user = userService.findByUserName(username);
        if(user == null){
            return null;
        }
        return new SimpleAuthenticationInfo(user,user.getPassword(),this.getName());
    }
}

这是我写的另一个 MyRealm 配置,简单看看了解一下就可。后期还会继续更新,这篇博客开篇也说了,是搭建项目基本框架。后期后在文末添加新的博客链接,最近有时间抓紧谢谢,分享这份知识。
在这里插入图片描述

六、配置资源访问权限 ShiroConfig

ShiroFilterFactoryBean 方法主要配置某些资源需要哪些权限才能访问。以及配置默认的登录页面的 URL 地址。访问某些权限不足的资源跳转到哪个页面,都在这里配置。
DefaultWebSecurityManager 是一个安全管理器,后期我们要做密码加密操作(密码 + 盐 + MD5 + 哈希散列)的时候,我们就要在 DefaultWebSecurityManager() 方法中配置自定义的密码 管理器。

package com.zkr.mingyu.config;

import com.zkr.mingyu.shiro.MyRealm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

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

/**标注 @Configuration 注解,
 * 标注这是一个配置类,
 * 让项目启动时加载该配置类。
 */
@Configuration
public class ShiroConfig {

    /**
     * 创建ShiroFilterFactory
     * 设置权限规则 需要注入securityManage
     * @param securityManager
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        /**
         * Shiro内置过滤器,可实现权限相关的拦截器
         *      常用的过滤器:
         *      anon: 无需认证(登录) 可以访问
         *      authc: 必须认证才可以访问
         *      user:如果使用rememberMe的功能可以直接访问
         *      perms: 该资源必须得到资源权限才可以访问
         *      role: 该资源必须得到角色权限才可以访问
         */
        Map<String, String> filterMap = new LinkedHashMap<String,String>();
        filterMap.put("/login","anon");
        filterMap.put("/index","anon");
        filterMap.put("/*","authc");
        shiroFilterFactoryBean.setLoginUrl("/login");
        shiroFilterFactoryBean.setUnauthorizedUrl("/noauth");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
        return shiroFilterFactoryBean;
    }

    /**
     * 创建安全管理器,
     * 并为 securityManager 注入自定义的 Realm 类
     * @param realm
     * @return
     */
    @Bean
    public DefaultWebSecurityManager getSecurityManager(MyRealm realm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(realm);
        return securityManager;
    }

    /**
     * 配置自定义 Realm 类
     * @Bean 将 MyRealm 注入到 Spring 容器当中
     * @return
     */
    @Bean
    public MyRealm getMyRealm(){
        return new MyRealm();
    }

}

代码不多,简单写个 demo 后期一步一步完善,有时间继续更新新的文章。比如密码加密、资源权限细分、会话、缓存等。
后期写好了新的功能,我会放在文章头、文章尾附上链接。看完点个赞,你的赞就是我更新的动力!收藏文章,后期在文章头部有新博客的链接。

附上一句话 (共勉):
滚水看不到倒影,盛怒看不到真相。

猜你喜欢

转载自blog.csdn.net/uziuzi669/article/details/108420499