springboot整合shiro

springboot整合shiro

丁起男 56 2022-10-12

springboot整合shiro

依赖

<dependency>
	<groupId>org.apache.shiro</groupId>
	<artifactId>shiro-spring-boot-web-starter</artifactId>
	<version>1.9.0</version>
</dependency>

登录

自定义realm

@Component
public class MyRealm extends AuthorizingRealm { //AuthorizingRealm 继承于AuthenticatingRealm

    @Autowired
    private UserService userService;

    //自定义授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }

    //自定义认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //获取用户身份信息
        String username = token.getPrincipal().toString();
        //调用业务层获取用户信息
        User user = userService.getByName(username);
        //校验
        if (user != null){
            AuthenticationInfo info = new SimpleAuthenticationInfo(
                    token.getPrincipal(),
                    user.getPassword(),
                    ByteSource.Util.bytes("盐值"),
                    username
            );
            return info;
        }
        return null;
    }
}

SimpleAuthenticationInfo构造参数:

  1. 保存的用户凭证,可以是用户id、用户名甚至是用户对象(需要序列化),可以在subject.getPrincipal()、principalCollection.getPrimaryPrincipal()中进行获取
  2. 数据库中获取的密码,与token中的密码进行对比,匹配上了就通过,否则报异常
  3. 密码加密用的盐值,可选
  4. 当前realm的名字

配置类型

@Configuration
public class ShiroConfig {

    @Autowired
    private MyRealm myRealm;

    //配置SecurityManager
    @Bean
    public DefaultWebSecurityManager defaultWebSecurityManager(){
        //创建SecurityManager对象
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        //创建加密对象,设置相关属性
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        matcher.setHashAlgorithmName("md5");//算法名称
        matcher.setHashIterations(3);//迭代次数
        //将加密对象存储到myRealm种
        myRealm.setCredentialsMatcher(matcher);
        //将myRealm存入manager种
        manager.setRealm(myRealm);
        return manager;
    }

    //拦截范围
    @Bean
    public DefaultShiroFilterChainDefinition shiroFilterChainDefinition(){
        DefaultShiroFilterChainDefinition definition = new DefaultShiroFilterChainDefinition();
        //设置不认证可访问的资源
        definition.addPathDefinition("/login","anon");
        //设置需要进行登录认证的拦截范围
        definition.addPathDefinition("/**","authc");
        return definition;
    }
}

登录接口

	@GetMapping("login")
    public String login(String name,String pwd){
        //获取登录认证对象
        Subject subject = SecurityUtils.getSubject();
        //封装请求数据到token
        AuthenticationToken token = new UsernamePasswordToken(name,pwd);
        try {
            //调用login方法进行登录认证
            subject.login(token);
            return "登录成功";
        }catch (AuthenticationException e){
            e.printStackTrace();
            return "登录失败";
        }
    }

多realm

    @Bean
    public DefaultWebSecurityManager defaultWebSecurityManager(){
        //创建对象
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        //多realm策略
        ModularRealmAuthenticator authenticator = new ModularRealmAuthenticator();
        authenticator.setAuthenticationStrategy(new AllSuccessfulStrategy());//具体使用的策略
        manager.setAuthenticator(authenticator);//要在setRealms之前设置
        //创建加密对象,设置相关属性
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        matcher.setHashAlgorithmName("md5");//算法名称
        matcher.setHashIterations(3);//迭代次数
        //将加密对象存储到myRealm种
        myRealm.setCredentialsMatcher(matcher);
        myRealm2.setCredentialsMatcher(matcher);
        //将myRealm存入manager种
        manager.setRealms(Arrays.asList(myRealm,myRealm2));//按照先后顺序执行
        return manager;
    }

具体策略:

  • AllSuccessfulStrategy:所有都满足
  • AtLeastOneSuccessfulStrategy:至少满足一个(默认)
  • FirstSuccessfulStrategy:满足一个即可

记住我

修改配置文件

@Configuration
public class ShiroConfig {

    @Autowired
    private MyRealm myRealm;

    @Bean
    public DefaultWebSecurityManager defaultWebSecurityManager(){
        //......
        //记住我
        manager.setRememberMeManager(rememberMeManager());
        return manager;
    }

    //cookie属性设置
    public SimpleCookie rememberMeCookie(){
        SimpleCookie cookie = new SimpleCookie("cookieName");
        cookie.setPath("/");
        cookie.setHttpOnly(true);
        cookie.setMaxAge(30*24*60*60);
        return cookie;
    }
    //cookie管理对象
    private RememberMeManager rememberMeManager() {
        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
        //设置cookie
        cookieRememberMeManager.setCookie(rememberMeCookie());
        return cookieRememberMeManager;
    }

    //拦截范围
    @Bean
    public DefaultShiroFilterChainDefinition shiroFilterChainDefinition(){
        DefaultShiroFilterChainDefinition definition = new DefaultShiroFilterChainDefinition();
        //设置不认证可访问的资源
        definition.addPathDefinition("/login","anon");
        //设置需要进行登录认证的拦截范围
        definition.addPathDefinition("/**","user");//记住我需要使用user
        return definition;
    }
}

修改登录接口

添加rememberMe参数

    @GetMapping("login")
    public String login(@RequestParam String name,@RequestParam String pwd,@RequestParam(defaultValue = "false") Boolean rememberMe){
        //获取登录认证对象
        Subject subject = SecurityUtils.getSubject();
        //封装请求数据到token
        AuthenticationToken token = new UsernamePasswordToken(name,pwd,rememberMe);
        try {
            //调用login方法进行登录认证
            subject.login(token);
            return "登录成功";
        }catch (AuthenticationException e){
            e.printStackTrace();
            return "登录失败";
        }
    }

登出

修改拦截配置

    //拦截范围
    @Bean
    public DefaultShiroFilterChainDefinition shiroFilterChainDefinition(){
        DefaultShiroFilterChainDefinition definition = new DefaultShiroFilterChainDefinition();
        //设置不认证可访问的资源
        definition.addPathDefinition("/login","anon");
        //配置登出过滤器
        definition.addPathDefinition("/logout","logout");//注意拦截器的顺序
        //设置需要进行登录认证的拦截范围
        //definition.addPathDefinition("/**","authc");
        definition.addPathDefinition("/**","user");
        return definition;
    }

授权

修改Realm

@Component
public class MyRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;

    //自定义授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        //创建对象,封装当前登录用户的角色权限信息
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //获取用户身份信息
        String username = principals.getPrimaryPrincipal().toString();
        //存储角色
        List<String> roleCodeList = userService.getRoleCodeByUsername(username);
        info.addRoles(roleCodeList);
        //存储权限
        List<String> menuCodeList = userService.getMenuCodeByUsername(username);
        info.addStringPermissions(menuCodeList);
        //返回信息
        return info;
    }

    //自定义认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //......
    }
}

在接口上添加注解进行验证

注解参考:shiro-注解 | 路人丁 (dingqinan.com)

缓存

整合EhCache

导入依赖

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-ehcache</artifactId>
            <version>1.4.2</version>
        </dependency>

在resources下添加配置文件

ehcache/ehcache-shiro.xml

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
         updateCheck="false">
    <!--磁盘的缓存位置-->
    <diskStore path="java.io.tmpdir"/>
    <!--默认缓存-->
    <defaultCache maxEntriesLocalHeap="1000"
                  eternal="false"
                  timeToIdleSeconds="3600"
                  timeToLiveSeconds="3600"
                  overflowToDisk="false"/>

    <!-- 登录认证信息缓存:缓存用户的角色和权限 -->
    <cache name="loginRoleMenuCache"
           maxEntriesLocalHeap="2000"
            eternal="false"
            timeToIdleSeconds="600"
            timeToLiveSeconds="0"
            overflowToDisk="false"
            statistics="true"/>
</ehcache>

修改配置类

    @Bean
    public DefaultWebSecurityManager defaultWebSecurityManager(){
        //......
        //设置缓存
        manager.setCacheManager(getEhCacheManager());
        return manager;
    }

    //创建缓存管理器
    @SneakyThrows
    private EhCacheManager getEhCacheManager() {
        EhCacheManager ehCacheManager = new EhCacheManager();
        InputStream is = ResourceUtils.getInputStreamForPath("classpath:ehcache/ehcache-shiro.xml");
        CacheManager cacheManager = new CacheManager(is);
        ehCacheManager.setCacheManager(cacheManager);
        return ehCacheManager;
    }

整合redis

导入依赖

        <dependency>
            <groupId>org.crazycake</groupId>
            <artifactId>shiro-redis-spring-boot-starter</artifactId>
            <version>3.3.1</version>
        </dependency>

修改配置文件

    @Autowired
    private RedisSessionDAO redisSessionDAO;//shiro-redis提供的

    @Autowired
    private RedisCacheManager redisCacheManager;//shiro-redis提供的

    @Bean
    public DefaultWebSecurityManager defaultWebSecurityManager(){
        //......
        //设置缓存
        manager.setSessionManager(sessionManager());
        manager.setCacheManager(redisCacheManager);
        return manager;
    }

    @Bean
    public SessionManager sessionManager(){
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        sessionManager.setSessionDAO(redisSessionDAO);
        return sessionManager;
    }

会话管理

SessionManager会话管理器,负责创建和管理用户的会话(session)生命周期,它能够在任何环境中在本地管理用户会话,即使没有web/servlet/ejb容器,也一样可以保存会话

默认情况下,shiro会检测当前环境中现有的会话机制进行适配,如果没有,它会使用内置的会话管理器来提供会话管理

SessionDAO负责Session的持久化操作,允许session数据写入到后端持久化数据库

SessionManager由SecurityManager管理,shiro提供了三种实现

实现 说明
DefaultSessionManager 用于javese环境
ServletContainerSessionManager 用于web环境,直接使用servlet容器的会话
DefaultWebSessionManager 用于web环境,自己维护会话

操作

//获取session对象
Session session = SecurityUtils.getSubject().getSession();
//设置
session.setAttribute("key","value");
//获取
session.getAttribute("key");

controller中的request,在shiro过滤器中的doFilerInternal方法,被包装成ShiroHttpServletRequest

SecurityManager和SessionManager会话管理器决定session来源于ServletRequest还是由shiro管理的会话

无论是通过request.getSession还是subject.getSession获取到的session,两者是等价的

异常

参考:shiro-异常 | 路人丁 (dingqinan.com)