springboot整合shiro&shiro自定义过滤器

  关于shiro的简介与使用方法在shiro分类中已经使用过了,而且在spring中已经成功的整合了shiro。下面研究springboot+thymeleaf中使用shiro。

  spring整合shiro参考:https://www.cnblogs.com/qlqwjy/p/7257502.html

  springboot整合shiro实际上是将xml整合的方式转为Java配置方式。

 

  基于SpringDataJPA。

1.springboot整合shiro

1.系统权限需要的五张表(三个bean,SpringdataJPA自动创建中间表)

package cn.qlq.shiro.bean;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "system_shiro_permission")
public class Permission {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;

    private String name; // 权限名 唯一

    private String url; // 访问地址信息 唯一

    private String description; // 描述信息

  // get,setter
}

 

 

package cn.qlq.shiro.bean;

import java.util.List;

import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;

@Entity
@Table(name = "system_shiro_role")
public class Role {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;

    private String name; // 角色名 唯一

    private String description; // 描述信息

    @OneToMany(fetch = FetchType.EAGER)
    private List<Permission> permissions; // 一个用户角色对应多个权限

    // getter,setter

}

 

 

package cn.qlq.shiro.bean;

import java.util.List;

import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;

@Entity
@Table(name = "system_shiro_user")
public class ShiroUser {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;

    private String username;// 用户名 唯一
    private String password;// 用户密码

    @OneToMany(fetch = FetchType.EAGER)
    private List<Role> roles;// 用户角色 一个用户可能有一个角色,也可能有 多个角色

// getter,setter

}

 

2. pom文件增加如下配置:

        <!-- 整合shiro -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.3.2</version>
        </dependency>

 

 

3.mapper层采用SpringDataJPA自动创建的接口实现

package cn.qlq.shiro.mapper;

import org.springframework.data.jpa.repository.JpaRepository;

import cn.qlq.shiro.bean.Permission;

public interface PermissionMapper extends JpaRepository<Permission, Integer> {
    Permission findByName(String name);
}

 

 

package cn.qlq.shiro.mapper;

import org.springframework.data.jpa.repository.JpaRepository;

import cn.qlq.shiro.bean.Role;

public interface RoleMapper extends JpaRepository<Role, Integer> {
    Role findByName(String name);
}

 

 

package cn.qlq.shiro.mapper;

import org.springframework.data.jpa.repository.JpaRepository;

import cn.qlq.shiro.bean.ShiroUser;

public interface ShiroUserMapper extends JpaRepository<ShiroUser, Integer> {
    ShiroUser findByUsername(String username);

    ShiroUser findByUsernameAndPassword(String username, String password);
}

 

4.自定义Realm和Shiro配置

package cn.qlq.shiro;

import javax.annotation.PostConstruct;

import org.apache.shiro.SecurityUtils;
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.authc.credential.HashedCredentialsMatcher;
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 org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

import cn.qlq.shiro.bean.Permission;
import cn.qlq.shiro.bean.Role;
import cn.qlq.shiro.bean.ShiroUser;
import cn.qlq.shiro.mapper.ShiroUserMapper;

@Component
public class UserAuthRealm extends AuthorizingRealm {

    @Autowired
    private ShiroUserMapper shiroUserMapper;

    /**
     * 权限核心配置 根据数据库中的该用户 角色 和 权限
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        ShiroUser user = (ShiroUser) principals.getPrimaryPrincipal();
        for (Role role : user.getRoles()) { // 获取 角色
            authorizationInfo.addRole(role.getName()); // 添加 角色
            for (Permission permission : role.getPermissions()) { // 获取 权限
                authorizationInfo.addStringPermission(permission.getName());// 添加
                                                                            // 权限
            }
        }

        return authorizationInfo;
    }

    /**
     * 用户登陆凭证信息
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String username = (String) token.getPrincipal();
        ShiroUser user = shiroUserMapper.findByUsername(username);
        if (user == null) {
            return null;
        }

        AuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user, user.getPassword(), this.getName());
        return authenticationInfo;
    }

    // 清除缓存
    public void clearCache() {
        PrincipalCollection principalCollection = SecurityUtils.getSubject().getPrincipals();
        super.clearCache(principalCollection);
    }
}

 

 

package cn.qlq.shiro;

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

import org.apache.shiro.mgt.SecurityManager;
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.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import cn.qlq.shiro.bean.Permission;
import cn.qlq.shiro.mapper.PermissionMapper;

@Configuration
public class ShiroConfig {

    @Autowired
    private PermissionMapper permissionMapper;

    @Autowired
    private UserAuthRealm userAuthRealm;

    /**
     * 配置 资源访问策略 . web应用程序 shiro核心过滤器配置
     */
    @Bean
    public ShiroFilterFactoryBean factoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
        factoryBean.setSecurityManager(securityManager);
        factoryBean.setLoginUrl("/shiro/login.html");// 登录页
        // 首页(这个不需要设置,因为是JS登录之后自己重定向)
        // factoryBean.setSuccessUrl("/shiro/index.html");
        factoryBean.setUnauthorizedUrl("/shiro/unauthorized.html");// 未授权界面;

        // 自定义filter配置( 配置 拦截过滤器链)
        factoryBean.setFilterChainDefinitionMap(setFilterChainDefinitionMap());
        return factoryBean;
    }

    /**
     * 配置 SecurityManager,可配置一个或多个realm
     */
    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(userAuthRealm);
        // securityManager.setRealm(xxxxRealm);
        return securityManager;
    }

    /**
     * 开启shiro 注解支持. 使以下注解能够生效 : 需要认证
     * {@link org.apache.shiro.authz.annotation.RequiresAuthentication
     * RequiresAuthentication} 需要用户
     * {@link org.apache.shiro.authz.annotation.RequiresUser RequiresUser} 需要访客
     * {@link org.apache.shiro.authz.annotation.RequiresGuest RequiresGuest}
     * 需要角色 {@link org.apache.shiro.authz.annotation.RequiresRoles
     * RequiresRoles} 需要权限
     * {@link org.apache.shiro.authz.annotation.RequiresPermissions
     * RequiresPermissions}
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    /**
     * 配置 拦截过滤器链. map的键 : 资源地址 ; map的值 : 所有默认Shiro过滤器实例名 默认Shiro过滤器实例 参考 :
     * {@link org.apache.shiro.web.filter.mgt.DefaultFilter}
     */
    private Map<String, String> setFilterChainDefinitionMap() {
        Map<String, String> filterMap = new LinkedHashMap<>();
        // 注册 数据库中所有的权限 及其对应url
        List<Permission> allPermission = permissionMapper.findAll();// 数据库中查询所有权限
        for (Permission p : allPermission) {
            filterMap.put(p.getUrl(), "perms[" + p.getName() + "]"); // 拦截器中注册所有的权限
        }
        filterMap.put("/static/**", "anon"); // 公开访问的资源
        filterMap.put("/shiro/doLogin.html", "anon"); // 登录地址放开
        filterMap.put("/logout", "logout"); // 配置登出页,shiro已经帮我们实现了跳转
        filterMap.put("/**", "authc"); // 所有资源都需要经过验证
        return filterMap;
    }

}

 

 

5.控制层代码和登录代码

package cn.qlq.shiro.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("shiro")
public class ShiroIndexController {

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

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

    @RequestMapping("unauthorized")
    public String unauthorized() {
        return "shiro/unauthorized";
    }

}

 

 

package cn.qlq.shiro.controller;

import java.util.Date;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import cn.qlq.shiro.bean.ShiroUser;
import cn.qlq.shiro.mapper.ShiroUserMapper;
import cn.qlq.utils.JSONResultUtil;

@Controller
@RequestMapping("shiro")
public class ShiroLoginController {

    @Autowired
    private ShiroUserMapper shiroUserMapper;

    @RequestMapping("doLogin")
    @ResponseBody
    public JSONResultUtil<String> doLogin(String username, String password, HttpServletRequest request) {
        ShiroUser user = shiroUserMapper.findByUsernameAndPassword(username, password);
        if (user == null) {
            return new JSONResultUtil<>(false, "账号或者密码错误");
        }

        // shiro中进行登录
        Subject currentUser = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        currentUser.login(token);

        // 设置登录的user
        HttpSession session = request.getSession();
        session.setAttribute("user", user);

        return new JSONResultUtil<>(true, "ok");
    }
}

 

6. 前台登录界面和首页(thymeleaf界面+layui框架)

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8"/>
    <title>后台登录-X-admin2.0</title>
    <meta name="renderer" content="webkit|ie-comp|ie-stand"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
    <meta name="viewport" content="width=device-width,user-scalable=yes, minimum-scale=0.4, initial-scale=0.8,target-densitydpi=low-dpi" />
    <meta http-equiv="Cache-Control" content="no-siteapp" />

    <link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" />
    <link rel="stylesheet" th:href="${#httpServletRequest.getContextPath()+'/static/x-admin/css/font.css'}"/>
    <link rel="stylesheet" th:href="${#httpServletRequest.getContextPath()+'/static/x-admin/css/xadmin.css'}"/>
    <script type="text/javascript" th:src="${#httpServletRequest.getContextPath()+'/static/js/jquery.min.js'}"></script>
    <script th:src="${#httpServletRequest.getContextPath()+'/static/x-admin/lib/layui/layui.js'}" charset="utf-8"></script>
    <script type="text/javascript" th:src="${#httpServletRequest.getContextPath()+'/static/x-admin/js/xadmin.js'}"></script>
    
    <script type="text/javascript" th:src="${#httpServletRequest.getContextPath()+'/static/x-admin/MyJs/shiro/login.js'}"></script>
</head>
<body class="login-bg">
    <div class="login layui-anim layui-anim-up">
        <div class="message">x-admin2.0-管理登录(Shiro)</div>
        <div id="darkbannerwrap"></div>
        
        <form method="post" class="layui-form" >
            <input name="username" placeholder="用户名" value="admin"  type="text" lay-verify="required" class="layui-input" />
            <hr class="hr15"/>
            <input name="password" lay-verify="required" placeholder="密码" value="admin"  type="password" class="layui-input"/>
            <hr class="hr15"/>
            <input value="登录" lay-submit="xx" lay-filter="login" style="width:100%;" type="submit" />
            <hr class="hr20"/>
        </form>
    </div>
</body>
</html>

 

登录JS代码login.js

    $(function() {
        layui.use('form', function(){
          var form = layui.form;
          // layer.msg('玩命卖萌中', function(){
          //   //关闭后的操作
          //   });
          //监听提交
          form.on('submit(login)', function(data){
              //打印一下填写的值然后区后台进行登陆
            $.post("/shiro/doLogin.html",data.field,function(result){
                if(result!=null && result.success == true){
                    window.location = "/shiro/index.html";
                }else{
                    layer.msg(result.msg);
                }
            },'json');
            return false;
          });
        });
    })

 

首页index.html代码:

<!doctype html>

<html lang="zh_CN" xmlns:th="http://www.thymeleaf.org"
      xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
      
<head>
    <meta charset="UTF-8"/>
    <title>后台登录-X-admin2.0</title>
    <meta name="renderer" content="webkit|ie-comp|ie-stand"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
    <meta name="viewport" content="width=device-width,user-scalable=yes, minimum-scale=0.4, initial-scale=0.8,target-densitydpi=low-dpi" />
    <meta http-equiv="Cache-Control" content="no-siteapp" />
    

    <link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" />
    <link rel="stylesheet" th:href="${#httpServletRequest.getContextPath()+'/static/x-admin/css/font.css'}"/>
    <link rel="stylesheet" th:href="${#httpServletRequest.getContextPath()+'/static/x-admin/css/xadmin.css'}"/>
    <script type="text/javascript" th:src="${#httpServletRequest.getContextPath()+'/static/js/jquery.min.js'}"></script>
    <script th:src="${#httpServletRequest.getContextPath()+'/static/x-admin/lib/layui/layui.js'}" charset="utf-8"></script>
    <script type="text/javascript" th:src="${#httpServletRequest.getContextPath()+'/static/x-admin/js/xadmin.js'}"></script>
    
    
    <script type="text/javascript" th:src="${#httpServletRequest.getContextPath()+'/static/x-admin/MyJs/login.js'}"></script>

</head>
<body class="login-bg">
首页
</body>
</html>

 

2.thymeleaf整合Shiro标签

1.pom文件引入

        <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>1.2.1</version>
        </dependency>

 

2.ShiroConfig里增加如下bean

    @Bean
    public ShiroDialect shiroDialect() {
        return new ShiroDialect();
    }

 

3.thymeleaf首页改动如下:

<html lang="zh_CN" xmlns:th="http://www.thymeleaf.org"
      xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">

 

4.页面正常使用shiro标签

    <span shiro:authenticated="false" >
          <span>欢迎您:<span th:text="${session.user.username}"></span></span>
    </span>
    <shiro:hasRole name="usermanager">
        系统管理员
    </shiro:hasRole>
    
    <shiro:hasPermission name="user:add">
        有增加权限
    </shiro:hasPermission>
    
    <shiro:hasPermission name="user:update">
        有修改权限
    </shiro:hasPermission>

 

3.shiro自定义过滤器

有时候我们希望指定的请求执行特殊的过滤器,例如:

(1) shiro的过滤器链中增加如下策略:

FILTER_CHAIN_DEFINITION_MAP.put("/api/v1/**", "anon, api");

  以/api/v1/开头的所有请求都不用登录,然后会进去api拦截器。

(2)filterMaps 增加api过滤器

    // Filter工厂,设置对应的过滤条件和跳转条件
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        // 过滤器
        Map<String, Filter> filterMaps = new HashMap<>();
        filterMaps.put("authc", new ShiroAuthFilter());
        // 增加API接口的过滤器
        filterMaps.put("api", new ApiTokenFilter());
        shiroFilterFactoryBean.setFilters(filterMaps);

        // 定义处理规则
        shiroFilterFactoryBean.setFilterChainDefinitionMap(setFilterChainDefinitionMap());

        return shiroFilterFactoryBean;
    }

(3)API过滤器如下: 返回false则拒绝处理

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import org.apache.shiro.web.filter.PathMatchingFilter;

import com.alibaba.fastjson.JSONObject;
import com.zd.bx.utils.JSONResultUtil;
import com.zd.bx.utils.shiro.APITokenUtils;
import com.zd.bx.utils.web.WebUtils;

/**
 * 验证API接口携带的Token
 * 
 * @author Administrator
 *
 */
public class ApiTokenFilter extends PathMatchingFilter {

    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        if (!APITokenUtils.isValidToken(request)) {
            WebUtils.writeJsonToResponse(response, JSONObject.toJSONString(JSONResultUtil.error("访问拒绝,无效token")));
            return false;
        }

        return true;
    }

}

 

posted @ 2020-03-04 14:34  QiaoZhi  阅读(5094)  评论(0编辑  收藏  举报