SpringBoot+Shiro权限框架

[TOC]

本文主要记录Spring Boot与Shiro进行整合使用,实现强大的用户权限管理,其中涉及如何完成用户认证(即用户登录),用户授权,thymeleaf页面整合shiro权限标签等知识点。

Spring Boot与Shiro框架简介

Spring Boot框架简介

Spring的诞生是 Java 企业版(Java Enterprise Edition,JEE,也称 J2EE)的

轻量级代替品。无需开发重量级的 Enterprise JavaBean(EJB),Spring 为企业级

Java 开发提供了一种相对简单的方法,通过依赖注入和面向切面编程,用简单的Java 对象(Plain Old Java Object,POJO)实现了 EJB 的功能。

虽然 Spring 的组件代码是轻量级的,但它的配置却是重量级的。

所有Spring配置都代表了开发时的损耗。 因为在思考 Spring 特性配置和解决业务问题之间需要进行思维切换,所以写配置挤占了写应用程序逻辑的时间。除此之外,项目的依赖管理也是件吃力不讨好的事情。决定项目里要用哪些库就已经够让人头痛的了,你还要知道这些库的哪个版本和其他库不会有冲突,这难题实在太棘手。并且,依赖管理也是一种损耗,添加依赖不是写应用程序代码。一旦选错了依赖的版本,随之而来的不兼容问题毫无疑问会是生产力杀手。

Spring Boot 让这一切成为了过去。

Spring Boot 简化了基于Spring的应用开发,只需要“run”就能创建一个独立的、生产级别的Spring应用。Spring Boot为Spring平台及第三方库提供开箱即用的设置(提供默认设置),这样我们就可以简单的开始。多数Spring Boot应用只需要很少的Spring配置。

我们可以使用SpringBoot创建java应用,并使用java –jar 启动它,或者采用传统的war部署方式。

Spring Boot 主要目标是:

l 为所有 Spring 的开发提供一个从根本上更快的入门体验。

l 开箱即用,但通过自己设置参数,即可快速摆脱这种方式。

l 提供了一些大型项目中常见的非功能性特性,如内嵌服务器、安全、指标,健康检测、外部化配置等。

l 绝对没有代码生成,也无需 XML 配置。

Shiro框架简介

Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码学和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。

1、 Authentication 认证 ---- 用户登录

2、 Authorization 授权 --- 用户具有哪些权限

3、 Cryptography 安全数据加密

4、 Session Management 会话管理

5、 Web Integration web系统集成

6、 Interations 集成其它应用,spring、缓存框架

Spring Boot与Shiro整合实现用户认证

分析Shiro的核心API

Subject: 用户主体(把操作交给SecurityManager)

SecurityManager:安全管理器(关联Realm)

Realm:Shiro连接数据的桥梁

Spring Boot整合Shiro

导入shiro与spring整合依赖

修改pom.xml


        
            org.apache.shiro
            shiro-spring
            1.4.0
        

自定义Realm类

package cn.sitcat.springbootshiro.configs;

import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

/**
 * @author Hiseico
 * @date 2019/4/13 18:07
 * @desc 自定义Realm
 */
public class UserRealm extends AuthorizingRealm {
    /**
     * 执行授权逻辑
     *
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("执行授权:Authorization");
        return null;
    }

    /**
     * 执行认证逻辑
     *
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("执行认证:Authentiction");
    }
}

编写Shiro配置类

package cn.sitcat.springbootshiro.configs;

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;

/**
 * @author hiseico
 * @date 2019/4/13 17:58
 * @desc Shiro的配置类
 */
//声明该类为配置类
@Configuration
public class ShiroConfig {
    /**
     * 创建ShiroFilterFactoyBean
     */
    @Bean(name = "shiroFilterFactoryBean")
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("webSecurityManager") DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        //添加shiro内置过滤器
        /**
         * shiro内置过滤器,可以实现权限的拦截器
         * 常用的过滤器:
         *  anon:无需认证(登录)可以访问
         *  authc:必须认证才可以访问
         *  user:如果使用remeberMe的功能可以直接访问
         *  perms:该资源必须得到资源权限才可以访问
         *  role:该资源必须得到角色权限才可以访问
         */
        Map filterMap = new LinkedHashMap<>();
       /* filterMap.put("/add","authc");
        filterMap.put("/update","authc");
        */
        //使用统配的过滤器拦截多个url
        filterMap.put("/login", "anon");
        filterMap.put("/test", "anon");
        filterMap.put("/*", "authc");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);

        //修改拦截后跳转的url
        shiroFilterFactoryBean.setLoginUrl("/login");

        return shiroFilterFactoryBean;
    }


    /**
     * 创建DefaultWebSecurityManager
     *
     * @Qualifier 从ioc容器中找到这个对象
     */
    @Bean(name = "webSecurityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("realm") UserRealm userRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(userRealm);
        return securityManager;
    }

    /**
     * 创建Realm
     * 使用@Bean将该方法的返回值放到IOC容器中
     */
    @Bean(name = "realm")
    public UserRealm getShiroRealm() {
        return new UserRealm();
    }
}

实现用户认证(登录)操作

登录页面




    
    Title


用户名:
密码:

Controller登录逻辑

package cn.sitcat.springbootshiro.controller;

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;


/**
 * @author liusong
 * @date 2019/4/13 17:40
 * @desc
 */
@Controller
public class HelloController {
    @RequestMapping("/test")
    public String hello(Model model) {
        model.addAttribute("msg", "springboot project created success !");
        return "success";
    }

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

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

    /**
     * 登录操作
     *
     * @param name
     * @param password
     * @param model
     * @return
     */
    @RequestMapping("/login")
    public String login(String name, String password, Model model) {
        /**
         * 使用shiro编写认证的操作
         */
        //1.获取Subject
        Subject subject = SecurityUtils.getSubject();
        //2.封装用户数据
        UsernamePasswordToken token = new UsernamePasswordToken(name, password);
        try {
//3.执行登录方法
            subject.login(token);

        } catch (UnknownAccountException e) {
            //登录失败,用户名错误
            model.addAttribute("msg", "用户名不存在!");
            return "login";
        } catch (IncorrectCredentialsException e) {
            //登录失败,密码错误
            model.addAttribute("msg", "用户名或密码错误!");
            return "login";
        }
        return "redirect:/test";
    }
}

实现Realm的判断逻辑

package cn.sitcat.springbootshiro.configs;

import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

/**
 * @author Hiseico
 * @date 2019/4/13 18:07
 * @desc 自定义Realm
 */
public class UserRealm extends AuthorizingRealm {
    /**
     * 执行授权逻辑
     *
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("执行授权:Authorization");
        return null;
    }

    /**
     * 执行认证逻辑
     *
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("执行认证:Authentiction");

        //假设数据库的用户名和密码
        String name = "admin";
        String password = "admin";

        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        if (!name.equals(token.getUsername())) {
            //用户不存在
            return null; // shiro底层会抛出UnknowAccountException
        }


        //判断密码
        return new SimpleAuthenticationInfo("", password, "");
    }
}

整合MyBatis实现登录

导入mybatis相关的依赖

    
    
        com.alibaba
        druid
        1.0.9
    
    
    
        mysql
        mysql-connector-java
    
    
    
        org.mybatis.spring.boot
        mybatis-spring-boot-starter
        1.3.1
    

创建用户表

-- 创建用户表
CREATE TABLE `user`(
 `id` BIGINT(20) NOT NULL PRIMARY KEY AUTO_INCREMENT,
    `name` varchar(200),
    `password` varchar(200)
);

在Springboot中配置相关的JDBC连接、连接池及Mybatis包扫描

# 配置数据源
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

#jdbc url
spring.datasource.url=jdbc:mysql:///shiro

# 连接数据库用户名和密码
spring.datasource.username=root
spring.datasource.password=root

# 设置连接池类型
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource

# 配置MyBatis包扫描
mybatis.type-aliases-package=cn.sitcat.springbootshiro.dao

编写User实体

package cn.sitcat.springbootshiro.model;

/**
 * @author hiseico
 * @date 2019/4/17 21:16
 * @desc
 */
public class User {
    private Long id;

    private String name;

    private String password;

    public Long getId() {
        return id;
    }

    public void setId(Long 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 + '\'' +
                '}';
    }
}

UserMapper

package cn.sitcat.springbootshiro.dao;

import cn.sitcat.springbootshiro.model.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Component;

@Component
@Mapper
public interface UserMapper{
    @Select("select * from User where name = #{name}")
    User selectUserByName(@Param("name") String name);

}

UserService

package cn.sitcat.springbootshiro.service;

import cn.sitcat.springbootshiro.model.User;

/**
 * @author hiseico
 * @date 2019/4/17 21:40
 * @desc
 */
public interface UserService {
    /**
     * 根据用户名查询用户信息
     * @param name
     * @return
     */
    User getUserByName(String name);
}

这里可以使用@Mapper注解直接注入Mapper,也可以在SpringBoot启动类上加包扫描注解@MapperScan("cn.sitcat.springbootshiro.dao")

UserServiceImpl

package cn.sitcat.springbootshiro.service;

import cn.sitcat.springbootshiro.dao.UserMapper;
import cn.sitcat.springbootshiro.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @author hiseico
 * @date 2019/4/17 21:40
 * @desc
 */
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    /**
     * 根据用户名查询用户信息
     *
     * @param name
     * @return
     */
    @Override
    public User getUserByName(String name) {
        return userMapper.selectUserByName(name);
    }
}

修改UserRealm的认证方法 从数据库查询用户信息

    /**
     * 执行认证逻辑
     *
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("执行认证:Authentiction");

        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;

        User user = userService.getUserByName(token.getUsername());
        if (user == null) {
            //用户不存在
            return null; // shiro底层会抛出UnknowAccountException
        }

        //判断密码
        return new SimpleAuthenticationInfo("", user.getPassword(), "");
    }

SpringBoot与Shiro整合实现用户授权

使用Shiro内置过滤器拦截资源

实例需求:给添加和更新页面增加用户权限,当用户有相应页面的权限才能访问。
修改ShiroConfig配置类中的ShiroFilterFactoyBean的内容

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

        //添加shiro内置过滤器
        /**
         * shiro内置过滤器,可以实现权限的拦截器
         * 常用的过滤器:
         *  anon:无需认证(登录)可以访问
         *  authc:必须认证才可以访问
         *  user:如果使用remeberMe的功能可以直接访问
         *  perms:该资源必须得到资源权限才可以访问
         *  role:该资源必须得到角色权限才可以访问
         */
        Map filterMap = new LinkedHashMap<>();
       /* filterMap.put("/add","authc");
        filterMap.put("/update","authc");
        */
        //使用统配的过滤器拦截多个url
        filterMap.put("/login", "anon");
        filterMap.put("/test", "anon");

        //授权过滤器
        //注意:当授权拦截后,shiro会自动跳转到未授权页面
        filterMap.put("/add", "perms[user:add]");
//        filterMap.put("/update","perms[user:update]");

        //认证过滤器
        filterMap.put("/*", "authc");

        //设置未授权提示页面
        shiroFilterFactoryBean.setUnauthorizedUrl("/noAuth");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);

        //修改拦截后跳转的url
        shiroFilterFactoryBean.setLoginUrl("/login");
        return shiroFilterFactoryBean;
    }

在Controller中配置/noAuth页面跳转到无权限访问提示页面

    @RequestMapping("noAuth")
    public String noAuth(Model model) {
        model.addAttribute("msg", "无权限!");
        return "noAuth";
    }

完成Shiro的资源授权

修改UserRealm类的认证方法的实现

    /**
     * 执行授权逻辑
     *
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("执行授权:Authorization");

        //给资源进行授权
        SimpleAuthorizationInfo simpleAuthenticationInfo  = new SimpleAuthorizationInfo();
        //添加资源的授权字符串
        simpleAuthenticationInfo.addStringPermission("user:add");

        return simpleAuthenticationInfo;
    }

将写死的授权字符串改为动态获取

1.在User表中增加perms字段,存储用户权限,在model也增加相应的字段

id name password perms
1 root root user:add
1 admin admin user:update

在Mapper和中增加根据用户id查询用户信息的方法

Mapper

    @Select("select * from User where id = #{id}")
    User selectUserById(@Param("id") Long id);

Service

    /**
     * 根据id查询用户信息
     *
     * @param id
     * @return
     */
    @Override
    public User selectUserById(Long id) {
        return userMapper.selectUserById(id);
    }

4.修改UserRealm中授权方法

package cn.sitcat.springbootshiro.configs;

import cn.sitcat.springbootshiro.model.User;
import cn.sitcat.springbootshiro.service.UserService;
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;

/**
 * @author hiseico
 * @date 2019/4/13 18:07
 * @desc 自定义Realm
 */
public class UserRealm extends AuthorizingRealm {


    /**
     * 执行授权逻辑
     *
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("执行授权:Authorization");

        //给资源进行授权
        SimpleAuthorizationInfo simpleAuthenticationInfo = new SimpleAuthorizationInfo();
        //添加资源的授权字符串
        //simpleAuthenticationInfo.addStringPermission("user:add");


        //从数据库查询当前登录的用户的授权字符串
        Subject subject = SecurityUtils.getSubject();
    /**
     *此处获取的User对象为下面执行认证逻辑中调用
     * return new SimpleAuthenticationInfo(user, user.getPassword(), "");时
     * 将user对象放到改方法的principal参数中传递了过来,该参数的类型为Object
     */
        User user = (User) subject.getPrincipal();
        User dbUser = userService.selectUserById(user.getId());

        simpleAuthenticationInfo.addStringPermission(dbUser.getPerms());

        return simpleAuthenticationInfo;
    }

    @Autowired
    private UserService userService;

    /**
     * 执行认证逻辑
     *
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("执行认证:Authentiction");

        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;

        User user = userService.getUserByName(token.getUsername());
        if (user == null) {
            //用户不存在
            return null; // shiro底层会抛出UnknowAccountException
        }

        //判断密码
        return new SimpleAuthenticationInfo(user, user.getPassword(), "");
    }
}

thymeleaf和shiro标签整合使用

1.导入thymeleaf模板引擎对shiro的拓展

        
        
            com.github.theborakompanioni
            thymeleaf-extras-shiro
            2.0.0
        

2.配置ShiroDialect

在ShiroConfig配置类里面添加getShiroDialect方法

    /**
    * 配置shiroDialect,用于thymeleaf和shiro标签配置使用
    * @return
    */
   @Bean
   public ShiroDialect getShiroDialect(){
       return new ShiroDialect();
   }

3.在页面上使用shiro标签

让对应权限的用户只能看到相应的内容




    
    Title


用户添加功能:添加

用户编辑功能:编辑

你可能感兴趣的:(SpringBoot+Shiro权限框架)