springboot+shiro+redis实现博客权限管理(前后端分离)

博客地址:https://www.lqnb.xyz/
源码地址:https://github.com/memo012/ac-blog

springboot+shiro+redis实现博客权限管理(前后端分离)

一. 背景

现如今,项目的安全权限问题越来越受重视,比如springsecurity,shiro…都可以用于权限管理,被称为权限框架。此博客介绍spring boot整合shiro。

二. 环境

  • springboot1.x或springboot2.x
  • shiro 1.4.0版本
  • redis任意

三. 搭建步骤

1. 导包

        <!--spring整个shiro-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>
        
        <!-- shiro+redis缓存插件 -->
        <dependency>
            <groupId>org.crazycake</groupId>
            <artifactId>shiro-redis</artifactId>
            <version>3.1.0</version>
        </dependency>

        <!-- lombok  简化实体类中的set.get等方法的编写-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.6</version>
            <!--<scope>provided</scope>-->
        </dependency>

整个pom.xml中文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.qiang</groupId>
    <artifactId>shiro-redis</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>shiro-redis</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <mybatis.version>1.3.3</mybatis.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>${mybatis.version}</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <!--<scope>runtime</scope>-->
        </dependency>

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

        <!--阿里巴巴druid数据源-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.6</version>
        </dependency>

        <!--spring整个shiro-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>

        <!-- lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.6</version>
            <!--<scope>provided</scope>-->
        </dependency>
        <!--thymeleaf-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <!-- shiro+redis缓存插件 -->
        <dependency>
            <groupId>org.crazycake</groupId>
            <artifactId>shiro-redis</artifactId>
            <version>3.1.0</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

二. 自定义realm(AuthRealm)

package com.qiang.config;

import com.qiang.dao.UserDao;
import com.qiang.entity.PermissionEntity;
import com.qiang.entity.RoleEntity;
import com.qiang.entity.UserEntity;
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.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/**
 * @Author: qiang
 * @ProjectName: adminsystem
 * @Package: com.qiang.config
 * @Description:
 * @Date: 2019/7/28 0028 20:53
 **/
public class AuthRealm extends AuthorizingRealm {

    @Autowired
    private UserDao userDao;

    /**
     * 授权
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("授权 doGetAuthorizationInfo");
        // 获取用户信息
        UserEntity userEntity = (UserEntity) principals.getPrimaryPrincipal();
        UserEntity byUsername = userDao.findByUsername(userEntity.getUsername());
        List<String> roles = new ArrayList<>();
        List<String> permissions = new ArrayList<>();
        Set<RoleEntity> roleEntiries = byUsername.getRoleEntiries();
        System.out.println(roleEntiries.size());
        if(roleEntiries.size() > 0 && roleEntiries != null){
            for (RoleEntity r:
                 roleEntiries) {
                roles.add(r.getRname());
                if(r.getPermissionEntities().size() > 0 && r.getPermissionEntities() != null){
                    for (PermissionEntity p:
                         r.getPermissionEntities()) {
                        permissions.add(p.getName());
                        System.out.println(p.getName());
                    }
                }

            }
        }
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.addRoles(roles);
        simpleAuthorizationInfo.addStringPermissions(permissions);
        return simpleAuthorizationInfo;
    }

    /**
     * 认证
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("认证 doGetAuthenticationInfo");
       String username = (String)token.getPrincipal();
        System.out.println(username);
        UserEntity byUsername = userDao.findSimpleByUsername(username);

        String password = byUsername.getPassword();

        if(password == null || password.equals("")){
            return null;
        }
        System.out.println(password);
        //当前realm对象的name
        String realmName = getName();
        //盐值
        ByteSource credentialsSalt = ByteSource.Util.bytes(byUsername.getUsername());
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(byUsername, byUsername.getPassword(),credentialsSalt, realmName);
        return simpleAuthenticationInfo;
    }
}

三. sessionManager自定义会话管理(CustomSessionManager )

package com.qiang.config;

import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.Serializable;

/**
 * @Author: qiang
 * @ProjectName: adminsystem
 * @Package: com.qiang.config
 * @Description: shiro中sessionManager自定义会话管理
 * @Date: 2019/7/28 0028 21:48
 **/
public class CustomSessionManager extends DefaultWebSessionManager {

    private static final String AUTHORIZATION = "token";

    public CustomSessionManager() {
        super();
    }

    @Override
    protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
        String sessionId = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
        if (sessionId != null) {
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,
                    ShiroHttpServletRequest.COOKIE_SESSION_ID_SOURCE);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, sessionId);
            //automatically mark it valid here.  If it is invalid, the
            //onUnknownSession method below will be invoked and we'll remove the attribute at that time.
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
            return sessionId;
        } else {
            return super.getSessionId(request, response);
        }
    }

}

四. shiro配置文件(ShiroConfig.java)

package com.qiang.config;

import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.crazycake.shiro.RedisCacheManager;

import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @Author: qiang
 * @ProjectName: adminsystem
 * @Package: com.qiang.config
 * @Description:
 * @Date: 2019/7/28 0028 21:19
 **/
@Configuration
public class ShiroConfig {

    @Bean("shiroFilter")
    public ShiroFilterFactoryBean shiroFilter() {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        bean.setSecurityManager(securityManager());
        bean.setLoginUrl("/pub/need_login");
        // 登录成功 如何前后端分离(不调用)
        bean.setSuccessUrl("/");

        // 没有权限,未授权就会调用此方法 先登录-》再验证是否有权限
        bean.setUnauthorizedUrl("/pub/not_permit");

        // 设置自定义filter
        Map<String, Filter> filterMap = new LinkedHashMap<>();
        filterMap.put("roleOrFilter", new CustomRoleOrAuthorizationFilter());
        bean.setFilters(filterMap);

        // 拦截器路径
        Map<String, String> map = new LinkedHashMap<>();
        // 退出过滤器
        map.put("/logout", "logout");

        // 匿名访问 游客即可
        map.put("/pub/**", "anon");

        map.put("/index", "anon");

        // 认证才可访问
        map.put("/authc/**", "authc");

        // 管理员才可访问
        map.put("/admin/**", "roles[admin]");

        // 管理员才可访问 只需具备任意角色中一个即可访问(自定义AuthorizationFilter)
        // map.put("/admin/**", "roleOrFilter[admin,root]");

        // 管理员才可访问 默认必须具备全部角色才可访问
        // map.put("/admin/**", "roles[admin,root]");

        // 拥有某权限才可访问
        map.put("/video/update", "perms[video_update]");

        // 坑二 过滤器是顺序执行,从上而下,一般将/** 放到最底下

        // authc: 认证才可访问
        // anon: 匿名即可
        map.put("/**", "anon");

        bean.setFilterChainDefinitionMap(map);

        return bean;
    }

    @Bean("securityManager")
    public SecurityManager securityManager() {
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        // 如果前后端不分离,则注释掉即可
        manager.setSessionManager(sessionManager());
        // 使用自定义cacheManager
        manager.setCacheManager(redisCacheManager());
        // 推荐放到最后
        manager.setRealm(authRealm());
        return manager;
    }

    /**
     * 自定义realm
     *
     * @return
     */
    @Bean("authRealm")
    public AuthRealm authRealm() {
        AuthRealm authRealm = new AuthRealm();
        authRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return authRealm;
    }

    /**
     * 密码加解密规则
     *
     * @return
     */
    @Bean("hashedCredentialsMatcher")
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();

        // 散列算法:使用MD5加密
        hashedCredentialsMatcher.setHashAlgorithmName("MD5");

        // 散列次数
        hashedCredentialsMatcher.setHashIterations(1024);
        hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);
        return hashedCredentialsMatcher;

    }

    /**
     * 自定义sessionManager
     *
     * @return
     */
    @Bean("sessionManager")
    public SessionManager sessionManager() {
        CustomSessionManager customSessionManager = new CustomSessionManager();
        // 超时时间 默认30分钟,会话超时,方法里面的是单位是毫秒
//        customSessionManager.setGlobalSessionTimeout(60 * 1000);

        // 配置session持久化
        customSessionManager.setSessionDAO(redisSessionDAO());
        return customSessionManager;
    }

    /**
     * 设置redisManager
     * @return
     */
    public RedisManager getRedisManager(){
        RedisManager redisManager = new RedisManager();
        redisManager.setHost("192.168.233.133");
        redisManager.setPort(6379);
        return redisManager;
    }

    /**
     * 配置具体cache实现类
     * @return
     */
    @Bean("redisCacheManager")
    public RedisCacheManager redisCacheManager(){
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(getRedisManager());
        // 设置过期时间,单位是秒
        redisCacheManager.setExpire(60 * 30);
        return redisCacheManager;
    }

    /**
     * 自定义session持久化
     * @return
     */
    public RedisSessionDAO redisSessionDAO(){
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(getRedisManager());

        // 设置sessionId生成器
        redisSessionDAO.setSessionIdGenerator(new CustomSessionIdGenerator());
        return redisSessionDAO;
    }

    /**
     * 开启 Shiro aop 注解支持
     * 用代理方式;所以需要开启代码支持
     * @return advisor
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager());
        return advisor;
    }
}

五. 密码加密类(ShiroMD5.java)

package com.qiang.config;

import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.util.ByteSource;

/**
 * @Author: qiang
 * @ProjectName: adminsystem
 * @Package: com.qiang.shiro
 * @Description:shiroMD5加密类
 * @Date: 2019/6/21 0021 13:17
 **/
public class ShiroMD5 {

    public static Object MD5(String username,String password){
        String hashAlgorithName = "MD5";
        int hashIterations = 1024;//加密次数
        ByteSource credentialsSalt = ByteSource.Util.bytes(username);
        Object obj = new SimpleHash(hashAlgorithName, password, credentialsSalt, hashIterations);
        return obj;
    }

    public static void main(String[] args) {
        System.out.println(MD5("dong", "123456"));
    }
}

到此,整合完成

具体代码:https://github.com/memo012/ac-blog

公众号

希望大家多多关注,里面不定期发放干货
领取全套资料:回复关键字【666】
迈莫公众号

猜你喜欢

转载自blog.csdn.net/qq_41066066/article/details/100062743