欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring Boot+shiro+redis 安全框架权限认证前后端分离【完整版】总结

程序员文章站 2022-03-04 13:33:27
...

一、导入依赖只导入redis以及shiro

     <!--redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!--使授权注解起作用-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <!--shiro-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.3.2</version>
        </dependency>
        <dependency>
            <groupId>org.crazycake</groupId>
            <artifactId>shiro-redis</artifactId>
            <version>3.2.3</version>
        </dependency>

二、新建ShiroConfig配置类

import cn.suse.edu.wasterecovery.config.properties.HbwsProperties;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.session.SessionListener;
import org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator;
import org.apache.shiro.session.mgt.eis.SessionIdGenerator;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.apache.shiro.mgt.SecurityManager;
import org.springframework.util.Base64Utils;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;

/**
 * @author [email protected]
 * @date 2020/2/3 18:30
 */
@Slf4j
@Configuration
public class ShiroConfig {
    @Autowired
    private HbwsProperties hbwsProperties;

    @Value("${spring.redis.host}")
    private String host;
    @Value("${spring.redis.port}")
    private int port;
    @Value("${spring.redis.password:}")
    private String password;
    @Value("${spring.redis.timeout}")
    private int timeout;
    @Value("${spring.redis.database}")
    private int database;

    @Bean
    public ShiroRealm shiroRealm(){
        ShiroRealm shiroRealm = new ShiroRealm();
        return shiroRealm;
    }

    /**
     * shiro 中配置 redis 缓存
     *
     * @return RedisManager
     */
    private RedisManager redisManager() {
        RedisManager redisManager = new RedisManager();
        redisManager.setHost(host + ":" + port);
        if (StringUtils.isNotBlank(password))
            redisManager.setPassword(password);
        redisManager.setTimeout(timeout);
        redisManager.setDatabase(database);
        return redisManager;
    }


    @Bean
    public RedisSessionDAO redisSessionDAO() {
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager());
        return redisSessionDAO;
    }

    private RedisCacheManager cacheManager() {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        return redisCacheManager;
    }



    /**
     * rememberMe cookie 效果是重开浏览器后无需重新登录
     *
     * @return SimpleCookie
     */
    private SimpleCookie sessionIdCookie() {
        // 设置 cookie 名称,对应 login.html 页面的 <input type="checkbox" name="rememberMe"/>
        SimpleCookie cookie = new SimpleCookie("rememberMe");
        // 设置 cookie 的过期时间,单位为秒,这里为一天
        cookie.setMaxAge(86400);
        //setcookie()的第七个参数
        //设为true后,只能通过http访问,javascript无法访问
        //防止xss读取cookie
        cookie.setHttpOnly(true);
        return cookie;
    }



    /**
     * 路径过滤规则
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager")SecurityManager securityManager) {
        DefaultWebSecurityManager securityManage =  new DefaultWebSecurityManager();
        //设置自定义realm.
        securityManage.setRealm(shiroRealm());
        //设置安全管理器
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 设置 securityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 登录的 url
        shiroFilterFactoryBean.setLoginUrl("/login");
        // 登录成功后跳转的 url
        shiroFilterFactoryBean.setSuccessUrl("/");
        // 未授权 url        shiroFilterFactoryBean.setUnauthorizedUrl("/login");
        /**
         *      保证顺序使用LinkedHashMap
         *      过滤链定义,从上向下顺序执行,一般将/**放在最为下边
         *      进行身份认证后才能访问
         * authc:   所有url都必须认证通过才可以访问;
         * anon:    所有url都都可以匿名访问
         * user:    如果使用rememberMe的功能可以直接访问
         * perms:   该资源必须授权
         * role:    该资源必须得到角色权限才可以访问
         */
        LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        // 设置免认证 url  
        String[] anonUrls =     StringUtils.splitByWholeSeparatorPreserveAllTokens("/login,/clien/**", ",");
        for (String url : anonUrls) {
            filterChainDefinitionMap.put(url, "anon");
        }

        // 配置退出过滤器,其中具体的退出代码 Shiro已经替我们实现了也可以自己设置
        filterChainDefinitionMap.put("/user/logout", "logout");
        // 除上以外所有 url都必须认证通过才可以访问,未通过认证自动访问 LoginUrl
        filterChainDefinitionMap.put("/**", "user");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        log.info("---------------shirofactory创建成功");
        return shiroFilterFactoryBean;
    }


    @Bean
    public SecurityManager securityManager(ShiroRealm shiroRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 配置 SecurityManager,并注入 shiroRealm
        securityManager.setRealm(shiroRealm);
        /**
         *  session管理
         */
        // 配置 SecurityManager,并注入 shiroRealm
        securityManager.setRealm(shiroRealm);
        // 配置 shiro session管理器
        securityManager.setSessionManager(sessionManager());
        // 配置 缓存管理类 cacheManager
        securityManager.setCacheManager(cacheManager());
        // 配置 rememberMeCookie
        securityManager.setRememberMeManager(rememberMeManager());
        return securityManager;
    }

    /**
     * cookie管理对象
     *
     * @return CookieRememberMeManager
     */
    private CookieRememberMeManager rememberMeManager() {
        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
        Collection<SessionListener> listeners = new ArrayList<SessionListener>();
        cookieRememberMeManager.setCookie(sessionIdCookie());
        // rememberMe cookie 加密的**
        String encryptKey = "hbws_shiro_key";
        byte[] encryptKeyBytes = encryptKey.getBytes(StandardCharsets.UTF_8);
        String rememberKey = Base64Utils.encodeToString(Arrays.copyOf(encryptKeyBytes, 16));
        cookieRememberMeManager.setCipherKey(Base64.decode(rememberKey));
        return cookieRememberMeManager;
    }



    /**
     * session 管理对象
     *
     * @return DefaultWebSessionManager
     */
    @Bean
    public DefaultWebSessionManager sessionManager() {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        Collection<SessionListener> listeners = new ArrayList<>();
        //配置监听
        listeners.add(sessionListener());
        // 设置 session超时时间我这里设置了60秒
        sessionManager.setGlobalSessionTimeout(60* 1000L);
        sessionManager.setSessionListeners(listeners);
        sessionManager.setSessionDAO(redisSessionDAO());
        sessionManager.setSessionIdUrlRewritingEnabled(false);
        return sessionManager;
    }


    /**
     * 配置会话ID生成器
     * @return
     */
    @Bean
    public SessionIdGenerator sessionIdGenerator() {
        return new JavaUuidSessionIdGenerator();
    }




    /**
     * 开启Shiro注解模式,可以在Controller中的方法上添加注解
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }


    /**
     * 配置session监听
     * @return
     */
    @Bean("sessionListener")
    public ShiroSessionListener sessionListener(){
        ShiroSessionListener sessionListener = new ShiroSessionListener();
        return sessionListener;
    }



}

三、新建ShiroSessionListener类

import org.apache.shiro.session.Session;
import org.apache.shiro.session.SessionListener;
import java.util.concurrent.atomic.AtomicInteger;


/**
 * @author 73642
 */
public class ShiroSessionListener implements SessionListener{

	private final AtomicInteger sessionCount = new AtomicInteger(0);
	
	@Override
	public void onStart(Session session) {
		sessionCount.incrementAndGet();
	}

	@Override
	public void onStop(Session session) {
		sessionCount.decrementAndGet();
	}

	@Override
	public void onExpiration(Session session) {
		sessionCount.decrementAndGet();
	}
	/**
	 * 获取在线人数使用
	 * @return
	 */
	public AtomicInteger getSessionCount() {
		return sessionCount;
	}
}

四、新建ShiroHelper

import cn.suse.edu.wasterecovery.config.annotation.Helper;
import org.apache.shiro.authz.AuthorizationInfo;

/**
 * @author [email protected]
 * @date 2020/2/3 18:04
 */
@Helper
public class ShiroHelper extends ShiroRealm {
    /**
     * 获取当前用户的角色和权限集合
     *
     * @return AuthorizationInfo
     */
    public AuthorizationInfo getCurrentuserAuthorizationInfo() {
        return super.doGetAuthorizationInfo(null);
    }
}

 

五、自定义realm,在我们自定义realm中实现了AuthorizingRealm接口,将其方法进行重写,将各种权限对用户进行授权,同时对用户身份进行验证,代码如下,每一行代码具体含义十分详细了。

import cn.suse.edu.wasterecovery.dto.MenuListDto;
import cn.suse.edu.wasterecovery.dto.UserDto;
import cn.suse.edu.wasterecovery.entity.Role;
import cn.suse.edu.wasterecovery.service.UserService;
import cn.suse.edu.wasterecovery.service.shiro.MenuService;
import cn.suse.edu.wasterecovery.service.shiro.RoleService;
import cn.suse.edu.wasterecovery.utils.BasesUtil;
import cn.suse.edu.wasterecovery.utils.EncryptUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.StringUtils;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author [email protected]
 * @date 2020/2/3 16:48
 * 自定义实现 ShiroRealm,包含认证和授权两大模块
 *
 */

@Component
@Slf4j
@RestController
public class ShiroRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;
    @Autowired
    private RoleService roleService;
    @Autowired
    private MenuService menuService;

    /**
     * 执行授权逻辑
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        log.info("---------------- 执行 Shiro 权限获取 ---------------------");
        UserDto user = (UserDto) SecurityUtils.getSubject().getPrincipal();
        String userName = user.getUsername();
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        // 获取用户角色集
        List<Role> roleList = this.roleService.findUserRole(userName);
        Set<String> roleSet = roleList.stream().map(Role::getRoleName).collect(Collectors.toSet());
        simpleAuthorizationInfo.setRoles(roleSet);
        // 获取用户权限集
        List<MenuListDto> permissionList = this.menuService.findUserPermissions(userName);
        Set<String> permissionSet = permissionList.stream().map(MenuListDto::getPerms).collect(Collectors.toSet());
        simpleAuthorizationInfo.setStringPermissions(permissionSet);
        log.info("---- 获取到以下权限 ----");
        log.info(String.valueOf(permissionSet));
        log.info("---------------- Shiro 权限获取成功 ----------------------");
        return simpleAuthorizationInfo;
    }


    /**
     * 执行认证逻辑
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        log.info("---------------- 执行 Shiro 凭证认证 ----------------------");
        
        UserDto userDto=null;
        // 获取用户输入的用户名和密码
        String userName = (String) token.getPrincipal();
        String password = new String((char[]) token.getCredentials());
            // 通过用户名到数据库查询用户信息
                userDto = this.userService.findByName(userName);
            if (userDto == null)
                throw new UnknownAccountException("用户名错误");
            if (!StringUtils.equals(EncryptUtil.SHA1(password), userDto.getPassword()))
                throw new IncorrectCredentialsException();
            if (UserDto.STATUS_LOCK.equals(userDto.getStatus()))
                throw new LockedAccountException();
        log.info("---------------- Shiro 凭证认证成功 ----------------------");
        return new SimpleAuthenticationInfo(userDto, password, getName());
    }
}

六、新建ShiroHelper并继承ShiroRealm,用于认证成功后权限拦截

import cn.suse.edu.wasterecovery.config.annotation.Helper;
import org.apache.shiro.authz.AuthorizationInfo;

/**
 * @author [email protected]
 * @date 2020/2/3 18:04
 */
@Helper
public class ShiroHelper extends ShiroRealm {
    /**
     * 获取当前用户的角色和权限集合
     *
     * @return AuthorizationInfo
     */
    public AuthorizationInfo getCurrentuserAuthorizationInfo() {
        return super.doGetAuthorizationInfo(null);
    }
}

七、controller测试

1、登录

    @PostMapping("login")
    public String UserLogin( UserLoginVo userVo, HttpServletRequest request){
        /**
         * 使用shiro编写认证
         */
        //获取Subject
        Subject userSubject = SecurityUtils.getSubject();
        //封装用户数据 userVo.isRememberMe()为true实现保存本地cokie,关闭浏览器再次打开无需再次登录
        UsernamePasswordToken token = new UsernamePasswordToken(userVo.getUsername(), userVo.getPassword(), userVo.isRememberMe());
        try {
            // 执行登录方法
            userSubject.login(token);
            return Result.toJsonP(userDtos);
        } catch (UnknownAccountException e) {
            return Result.run_false("用户名不存在");
        } catch (LockedAccountException e) {
            return Result.run_false("账号已被锁定,请联系管理员!");
        } catch (IncorrectCredentialsException e) {
            return Result.run_false("密码错误");
        }
    }

2、权限测试新建controller

    @GetMapping("test")
    @RequiresPermissions("shiro:test")
    public String shiroTest(AddSunMenuVo addSunMenuDto) {
        return "需要权限";
    }



    @GetMapping("test2")
    public String shiroTest2(AddSunMenuVo addSunMenuDto) {
        return "无需权限";
    }

有不明白的希望提出来,才开始写博客,希望越来越好

相关标签: java