Spring 开发SpringFrameworkshiro

《Shiro 一》SpringBoot + Shiro 构建We

2019-01-01  本文已影响5人  殷天文

Shiro 一款简单易用,功能强大的安全框架,帮助我们安全高效的构建企业级应用。之前几个项目都用到过 Shiro,最近抽空梳理了一下,分享一些经验。

本文demo:https://gitee.com/yintianwen7/taven-springboot-learning/tree/master/springboot-shiro
本文demo选型:thymeleaf, springboot 2, shiro, ehcache
PS:如果我不拖延的话,估计还是会有后续的 :)

目录
Shiro 能做什么
Shiro 常用组件介绍
Shiro 是如何工作的
Shiro 如何集成
关于 thymeleaf-extras-shiro

Shiro 能做什么
Shiro 常用组件介绍
image.png
Shiro 是如何工作的

简单来讲的话,在Spring项目中

  1. Shiro 会将他的所有组件注册到 SecurityManager
  2. 再通过将 SecurityManager 注册到 ShiroFilterFactoryBean(这个类实现了Spring 的BeanPostProcessor会预先加载) 中,
  3. 最后以 filter 的形式出现(实现了Spring的FactoryBean,构造一个 filter 注册到 Spring 容器中),实现用户权限的管理。
Shiro 如何集成
<!--shiro-->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>1.4.0</version>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.4.0</version>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-ehcache</artifactId>
    <version>1.4.0</version>
</dependency>
<!-- 基于thymeleaf的shiro扩展 -->
<dependency>
    <groupId>com.github.theborakompanioni</groupId>
    <artifactId>thymeleaf-extras-shiro</artifactId>
    <version>2.0.0</version>
</dependency>
@Configuration
public class ShiroConfig {

    private static final Logger log = LoggerFactory.getLogger(ShiroConfig.class);

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        shiroFilter.setSecurityManager(securityManager);

        Map<String, String> chainDefinition = new LinkedHashMap<>();
        // 静态资源与登录请求不拦截
        chainDefinition.put("/js/**", "anon");
        chainDefinition.put("/css/**", "anon");
        chainDefinition.put("/img/**", "anon");
        chainDefinition.put("/layui/**", "anon");
        chainDefinition.put("/login", "anon");
        chainDefinition.put("/login.html", "anon");
        // 用户为授权通过认证 && 包含'admin'角色
        chainDefinition.put("/admin/**", "authc, roles[super_admin]");
        // 用户为授权通过认证或者RememberMe && 包含'document:read'权限
        chainDefinition.put("/docs/**", "user, perms[document:read]");
        // 用户访问所有请求 授权通过 || RememberMe
        chainDefinition.put("/**", "user");

        shiroFilter.setFilterChainDefinitionMap(chainDefinition);
        // 当 用户身份失效时重定向到 loginUrl
        shiroFilter.setLoginUrl("/login.html");
        // 用户登录后默认重定向请求
        shiroFilter.setSuccessUrl("/index.html");
        return shiroFilter;
    }

    @Bean
    public Realm realm() {
        ShiroRealm realm = new ShiroRealm();
        realm.setCredentialsMatcher(credentialsMatcher());
        realm.setCacheManager(ehCacheManager());
        return realm;
    }

    @Bean
    public CacheManager ehCacheManager() {
        EhCacheManager cacheManager = new EhCacheManager();
        cacheManager.setCacheManagerConfigFile("classpath:ehcache.xml");
        return cacheManager;
    }

    @Bean
    public CredentialsMatcher credentialsMatcher() {
        AuthCredentialsMatcher credentialsMatcher = new AuthCredentialsMatcher(ehCacheManager());
        credentialsMatcher.setHashAlgorithmName(AuthCredentialsMatcher.HASH_ALGORITHM_NAME);
        credentialsMatcher.setHashIterations(AuthCredentialsMatcher.HASH_ITERATIONS);
        credentialsMatcher.setStoredCredentialsHexEncoded(true);
        return credentialsMatcher;
    }

    @Bean
    public DefaultWebSecurityManager securityManager() {
        log.debug("--------------shiro已经加载----------------");
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setCacheManager(ehCacheManager());
        manager.setRealm(realm());
        manager.setRememberMeManager(rememberMeManager());
        return manager;
    }

    @Bean
    public RememberMeManager rememberMeManager() {
        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
        //rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位)
        cookieRememberMeManager.setCipherKey(Base64.decode("2AvVhdsgUs0FSA3SDFAdag=="));
        cookieRememberMeManager.setCookie(rememberMeCookie());
        return cookieRememberMeManager;
    }

    @Bean
    public SimpleCookie rememberMeCookie(){
        //这个参数是cookie的名称,对应前端的checkbox的name = rememberMe
        SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
        //<!-- 记住我cookie生效时间30天 ,单位秒;-->
        simpleCookie.setMaxAge(259200);
        return simpleCookie;
    }

    /**
     * Shiro生命周期处理器:
     * 用于在实现了Initializable接口的Shiro bean初始化时调用Initializable接口回调(例如:UserRealm)
     * 在实现了Destroyable接口的Shiro bean销毁时调用 Destroyable接口回调(例如:DefaultSecurityManager)
     */
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    /**
     * 启用shrio授权注解拦截方式,AOP式方法级权限检查
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor =
                new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    /**
     * thymeleaf的shiro扩展
     *
     * @return
     */
    @Bean
    public ShiroDialect shiroDialect() {
        return new ShiroDialect();
    }

}

以上基本是Spring项目集成 Shiro 的通用配置,下面针对上述的几个Bean 聊一聊
1. ShiroFilterFactoryBean:用于定义 请求的拦截规则, Shiro为我们默认提供了一些选项,常用如下

Filter Name Class
anon org.apache.shiro.web.filter.authc.AnonymousFilter
authc org.apache.shiro.web.filter.authc.FormAuthenticationFilter
authcBasic org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
logout org.apache.shiro.web.filter.authc.LogoutFilter
noSessionCreation org.apache.shiro.web.filter.session.NoSessionCreationFilter
perms org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
port org.apache.shiro.web.filter.authz.PortFilter
rest org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
roles org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
ssl org.apache.shiro.web.filter.authz.SslFilter
user org.apache.shiro.web.filter.authc.UserFilter

2. Realm:上面提到过Realm是用于连接Shiro和客户系统的用户数据的桥梁, 我们通过实现AuthorizingRealm 来提供用户认证和授权两个API

public class ShiroRealm extends AuthorizingRealm {

    private static final Logger log = LoggerFactory.getLogger(AuthorizingRealm.class);

    @Autowired
    @Lazy // 这里lazy 是有必要的, shiro组件会预先加载,导致依赖的bean 没有生成代理对象(AOP失效)
    private UserService userService;

    /**
     * 认证
     *
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        String username = (String) authenticationToken.getPrincipal();

        if (log.isDebugEnabled()) {
            log.debug(String.format("user:%s executing doGetAuthenticationInfo", username));
        }

        User user = userService.getUserByUsername(username);

        if (user == null) {
            throw new UnknownAccountException();
        }

        if (Constant.IS_LOCK.equals(user.getIsLock())) {
            throw new LockedAccountException();
        }

        // ShiroUser 作为实际的 principal
        ShiroUser shiroUser = new ShiroUser();
        BeanUtils.copyProperties(user, shiroUser);

        // SimpleAuthenticationInfo(Object principal, Object credentials, String realmName)
        // principal 会被封装到 subject 中
        // shiro 默认会把我们的 credentials (也就是password) 和 token 中的作对比,所以我们可以不用做密码校验
        ByteSource salt = ByteSource.Util.bytes(user.getUsername());
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(shiroUser, user.getPassword(), salt, getName());

        if (log.isDebugEnabled()) {
            log.debug(String.format("user:%s executed doGetAuthenticationInfo", username));
        }

        return info;
    }

    /**
     * 授权
     *
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        ShiroUser shiroUser = (ShiroUser) principalCollection.getPrimaryPrincipal();

        if (log.isDebugEnabled()) {
            log.debug(String.format("user:%s executing doGetAuthorizationInfo", shiroUser.getUsername()));
        }

        AuthorizationDTO authorizationDTO = userService.getRolesAndPermissions(shiroUser.getId());

        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addRoles(authorizationDTO.getRoleCodeSet());
        info.addStringPermissions(authorizationDTO.getPermissionCodeSet());

        if (log.isDebugEnabled()) {
            log.debug(String.format("user:%s executed doGetAuthorizationInfo", shiroUser.getUsername()));
        }

        return info;
    }

}

3. CredentialsMatcher: 密码匹配器,用于匹配 doGetAuthenticationInfo 方法返回的 credentials 和 subject.login(token);时的 token 中的 password是否一致。常用的实现有 SimpleCredentialsMatcher(默认是该实现)、HashedCredentialsMatcher (该实现可以进行加密匹配)

4. DefaultWebSecurityManager:如上述,用于协调Shiro内部各种安全组件,我们需要将我们扩展的bean 注册到 SecurityManager 中

5. RememberMeManager:开启该组件后使用记住我服务, token 中 rememberMe 为 true 时,登录成功之后会创建RememberMe cookie。

其余参考上文代码注释

关于 thymeleaf-extras-shiro

Shiro 默认支持在 jsp 中使用 shiro标签。但是想在 thymeleaf 中使用 Shiro 标签呢?

使用 thymeleaf-extras-shiro 完美解决 thymeleaf 颗粒化权限控制

你好, <span th:text="${principal}"></span><br>
<p shiro:hasRole="super_admin">当前角色超级管理员</p>
<button shiro:hasPermission="'sys:user:add'">添加</button>
<button shiro:hasPermission="'sys:user:update'">编辑</button>
<button shiro:hasPermission="'sys:user:lock'">冻结</button>
<div shiro:hasAllPermissions="'sys:user:add, sys:user:update, sys:user:lock'">
    <span>满足所有权限时显示</span>
</div>
<div shiro:hasAnyPermissions="'sys:user:add, sys:user:update, sys:user:lock'">
    <span>满足一个权限即可显示</span>
</div>

更多用法参考
Github 文档:https://github.com/theborakompanioni/thymeleaf-extras-shiro

本文demo:https://gitee.com/yintianwen7/taven-springboot-learning/tree/master/springboot-shiro
如果你发现我的文章或者demo中存在问题,请联系我

上一篇 下一篇

猜你喜欢

热点阅读