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. 导包

        
        
            org.apache.shiro
            shiro-spring
            1.4.0
        
        
        
        
            org.crazycake
            shiro-redis
            3.1.0
        

        
        
            org.projectlombok
            lombok
            1.18.6
            
        

整个pom.xml中文件



    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        2.1.6.RELEASE
         
    
    com.qiang
    shiro-redis
    0.0.1-SNAPSHOT
    shiro-redis
    Demo project for Spring Boot

    
        1.8
        1.3.3
    

    
        
            org.springframework.boot
            spring-boot-starter-web
        
        
            org.mybatis.spring.boot
            mybatis-spring-boot-starter
            ${mybatis.version}
        

        
            mysql
            mysql-connector-java
            
        

        
            org.springframework.boot
            spring-boot-starter-test
            test
        

        
        
            com.alibaba
            druid
            1.1.6
        

        
        
            org.apache.shiro
            shiro-spring
            1.4.0
        

        
        
            org.projectlombok
            lombok
            1.18.6
            
        
        
        
            org.springframework.boot
            spring-boot-starter-thymeleaf
        
        
        
            org.crazycake
            shiro-redis
            3.1.0
        
    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    


二. 自定义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 roles = new ArrayList<>();
        List permissions = new ArrayList<>();
        Set 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 filterMap = new LinkedHashMap<>();
        filterMap.put("roleOrFilter", new CustomRoleOrAuthorizationFilter());
        bean.setFilters(filterMap);

        // 拦截器路径
        Map 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】
迈莫公众号

你可能感兴趣的:(springboot,Java)