SpringBoot整合Shiro实现权限控制

一、SpringBoot整合Shiro

Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、受权、密码和会话管理。html

1.一、shiro简介

shiro有个核心组件,分别为SubjectSecurityManagerRealmsjava

  • Subject:至关于当前操做的”用户“,这个用户不必定是一个具体的人,是一个抽象的概念,代表的是和当前程序进行交互的任何东西,例如爬虫、脚本、等等。全部的Subject都绑定到SecurityManager上,与 Subject 的全部交互都会委托给 SecurityManager;能够把 Subject 认为是一个门面;SecurityManager 才是实际的执行者。
  • SecurityManager:这个是shiro框架的核心,全部与安全相关的操做都会与它进行交互,它管理者全部的Subject。
  • Realms:充当了Shiro与应用安全数据间的”桥梁“,当对用户执行认证(登陆)和受权(访问控制)验证时,SecurityManager 须要从 Realm 获取相应的用户进行比较以肯定用户身份是否合法;也须要从 Realm 获得用户相应的角色 / 权限进行验证用户是否能进行操做。

若是想要更加深刻的了解的shrio能够参考:https://www.w3cschool.cn/shiro/co4m1if2.htmlgit

1.二、代码的具体实现

1.2.一、Maven的配置

<!--shiro-->
		<dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring-boot-starter</artifactId>
            <version>1.7.1</version>
        </dependency>
         <!--shiro整合thymeleaf-->
         <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>2.0.0</version>
        </dependency>
		<!--shiro缓存-->
		 <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-ehcache</artifactId>
            <version>1.7.1</version>
        </dependency>

shiro默认是与jsp进行使用的,而这里是shiro整合thymeleaf全部要导入shiro整合thymeleaf的jar包github

1.2.二、整合须要实现的类

通常来讲整合只须要完成两个类的实现便可
一个是 ShiroConfig 一个是 CustomerRealm
若是须要添加shiro缓存而且不是自带的缓存而是redis缓存还须要进行另外两个类的编写
一个是 RedisCache 一个是 RedisCacheManagerweb

1.2.三、项目结构

项目结构

1.2.四、ShiroConfig的实现

未加shiro的缓存redis

package com.yuwen.config;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import com.yuwen.shiro.cache.RedisCacheManager;
import com.yuwen.shiro.realm.CustomerRealm;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.realm.Realm;
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.HashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {

    //让页面shiro标签生效
    @Bean
    public ShiroDialect shiroDialect(){
        return new ShiroDialect();
    }

    //一、建立shiroFilter   负责拦截全部请求
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
        //给filter设置安全管理
        factoryBean.setSecurityManager(defaultWebSecurityManager);
        //配置系统的受限资源
        //配置系统公共资源 所有都能访问的设置anon
        Map<String,String> map = new HashMap<>();
        map.put("/main","authc");//请求这个资源须要认证和受权 authc表示须要认证后才能访问
        map.put("/admin","roles[admin]"); //表示admin角色才能访问 roles[]表示须要什么角色才能访问
        map.put("/manage","perms[user:*:*]"); //表示须要user:*:*权限才能访问 perms[]表示须要什么权限才能访问
        //访问须要认证的页面若是未登陆会跳转到/login路由进行登录
        factoryBean.setLoginUrl("/login");
        //访问未受权页面会自动跳转到/unAuth路由
        factoryBean.setUnauthorizedUrl("/unAuth");
        factoryBean.setFilterChainDefinitionMap(map);
        return factoryBean;
    }
    //二、建立安全管理器
    @Bean
    public DefaultWebSecurityManager defaultWebSecurityManager(@Qualifier("getRealm") Realm realm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //给安全管理器设置
        securityManager.setRealm(realm);
        return securityManager;
    }
    //三、建立自定义的realm
    @Bean
    public Realm getRealm(){
        CustomerRealm customerRealm = new CustomerRealm();
        //修改凭证校验匹配器
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        //设置加密算法为md5
        credentialsMatcher.setHashAlgorithmName("MD5");
        //设置散列次数
        credentialsMatcher.setHashIterations(1024);
        customerRealm.setCredentialsMatcher(credentialsMatcher);
        return customerRealm;
    }
}

由于通常在数据库中设置明文密码不安全,全部我这里对密码进行了md5加密,个人加密方式为:密码 = 密码+盐+散列次数 然后进行MD5加密 因此这里建立自定义的realm时须要进行设置匹配器这样登陆时密码才能匹配成功算法

1.2.五、CustomerRealm的实现

package com.yuwen.shiro.realm;

import com.yuwen.pojo.User;
import com.yuwen.pojo.vo.ViewPerms;
import com.yuwen.pojo.vo.ViewRole;
import com.yuwen.service.UserService;
import com.yuwen.shiro.salt.MyByteSource;
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.CollectionUtils;
import org.springframework.util.ObjectUtils;
import javax.annotation.Resource;
import java.util.List;

//自定义realm
public class CustomerRealm extends AuthorizingRealm {

    @Resource
    private UserService userService;
	//受权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //获取身份信息
        String primaryPrincipal = (String)principalCollection.getPrimaryPrincipal();
        //根据主身份信息获取角色 和 权限信息
        List<ViewRole> roles = userService.findRolesByUsername(primaryPrincipal);
        if (!CollectionUtils.isEmpty(roles)){
            SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
            roles.forEach(viewRole -> {
                simpleAuthorizationInfo.addRole(viewRole.getName());
                //权限信息
                List<ViewPerms> perms = userService.findPermsByRoleId(viewRole.getName());
                if (!CollectionUtils.isEmpty(perms)){
                    perms.forEach(viewPerms -> {
                        simpleAuthorizationInfo.addStringPermission(viewPerms.getPName());
                    });
                }
            });
            return simpleAuthorizationInfo;
        }
        return null;
    }
    
	//认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //获取登入的身份信息
        String principal = (String) authenticationToken.getPrincipal();
        User user = userService.findByUsername(principal);
        if (!ObjectUtils.isEmpty(user)){
            //ByteSource.Util.bytes(user.getSalt()) 经过这个工具将盐传入
            //若是身份认证验证成功,返回一个AuthenticationInfo实现;
            return new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(),new MyByteSource(user.getSalt()),this.getName());
        }
        return null;
    }
}

在登陆时会自动调用这个身份验证 在验证时若是出错,会报异常,我在controller层接收了异常并处理spring

controller层中登陆时的异常处理 数据库

@PostMapping("/login")
    public String login(String username,String password){
        //获取主体对象
        Subject subject = SecurityUtils.getSubject();
        try {
        	//自动调用CustomerRealm 类中的身份验证方法
            subject.login(new UsernamePasswordToken(username,password));
            return "index";
        }catch (UnknownAccountException e){ //接收异常并处理
            e.printStackTrace();
            model.addAttribute("msg","用户名有误,请从新登陆");
        }catch (IncorrectCredentialsException e){//接收异常并处理
            e.printStackTrace();
            model.addAttribute("msg","密码有误,请从新登陆");
        }
        return "login";
    }

1.2.六、shiro缓存配置

定义了shiro缓存,用户登陆后,其用户信息、拥有的角色 / 权限没必要每次去查,这样能够提升效率apache

默认缓存的配置

ShiroConfig中getRealm() 方法中开启缓存管理

@Bean
    public Realm getRealm(){
        CustomerRealm customerRealm = new CustomerRealm();
        //开启缓存管理
        customerRealm.setCacheManager(new EhCacheManager());
        //开启全局缓存
        customerRealm.setCachingEnabled(true);
        //开启认证缓存
        customerRealm.setAuthenticationCachingEnabled(true);
        customerRealm.setAuthenticationCacheName("authenticationCache");
        //开启权限缓存
        customerRealm.setAuthorizationCachingEnabled(true);
        customerRealm.setAuthorizationCacheName("authorizationCache");
        return customerRealm;
    }

与reids整合的缓存这里就不说明了,放在源码里本身查看,源码在下方

1.2.七、主页index.html的设置

在这里用标签来判断某些区域须要认证或什么角色或者什么权限才能访问

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
                xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
    <link rel="shortcut icon" href="#">
</head>
<body>
<h1>index</h1>
<a href="/logout">退出</a>
<div>
    <a href="/main">main</a> | <a href="/manage">manage</a> | <a href="/admin">admin</a>
</div>
<!--获取认证信息-->
用户:<span shiro:principal=""></span><hr>
<!--认证处理-->
<span shiro:authenticated=""><hr>
    显示认证经过内容
</span>
<span shiro:notAuthenticated=""><hr>
    没有认证时 显示
</span>
<!--受权角色-->
<span shiro:hasRole="admin"><hr>
    admin角色 显示
</span>
<span shiro:hasPermission="user:*:*"><hr>
    具备用户模块的"user:*:*"权限 显示
</span>
</body>
</html>

1.三、简单测试

公共页面

1.3.一、admin角色全部权限测试

admin角色

1.3.二、无角色有权限测试

有权限

1.3.三、无角色无权限测试

无角色无权限
能够访问的页面

1.4 项目源码

须要源码的在这:https://gitee.com/residual-temperature/shiro-demo

相关文章
相关标签/搜索