快速上手Shiro拦截器、认证、授权功能

本文主要是为让大家快速上手整合Shiro进入项目,完成拦截,认证,授权等功能,具体的原理可能并不会过多的描述请大家谅解 。

什么是Shiro

Shiro是一个功能强大且易于使用的Java安全框架,它执行身份验证、授权、加密和会话管理。使用Shiro易于理解的API,您可以快速轻松地保护任何应用程序—从最小的移动应用程序到最大的web和企业应用程序。

下文配置的安全管理器和会话管理器中有用到Redis实现的自定义缓存,大家可以要是有自定义缓存的需要可以参考下这篇文章:

Redis实现自定义Shiro的缓存管理器_六木老师的博客-CSDN博客

依赖

        
        
            org.apache.shiro
            shiro-spring-boot-starter
            ${shiro.version}
        

        
            nz.net.ultraq.thymeleaf
            thymeleaf-layout-dialect
            ${thymeleaf-layout-dialect.version}
        

        
            com.github.theborakompanioni
            thymeleaf-extras-shiro
            ${thymeleaf-extras-shiro.version}
        
        

Shiro使用自定义Relam实现认证\授权

@Slf4j
public class JwtRealm extends AuthorizingRealm {

    @Resource
    private JwtTokenUtil jwtTokenUtil;

    @DubboReference
    private ISysUserService userService;

    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JwtToken;
    }


    /**
     * 权限信息认证(包括角色以及权限)是用户访问controller的时候才进行验证(redis存储的此处权限信息)
     * 触发检测用户权限时才会调用此方法,例如checkRole,checkPermission
     *
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        log.info("===============Shiro权限认证开始============ [ roles、permissions]==========");
        Long userId = null;
        String loginName = null;
        if (null != principalCollection) {
            LoginUserVo loginUserVoVo = (LoginUserVo) principalCollection.getPrimaryPrincipal();
            userId = loginUserVoVo.getId();
            loginName = loginUserVoVo.getLoginName();
        }
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

        //TODO: 设置用户拥有的角色集合
        Set roleSet = userService.selectUserRoles(userId);
        info.setRoles(roleSet);

        //TODO: 设置用户拥有的权限集合
        Set permissionSet = userService.selectUserPermissions(userId);
        info.addStringPermissions(permissionSet);
        log.info("===============Shiro权限认证成功==============");
        return info;
    }

    /**
     * 用户信息认证是在用户进行登录的时候进行验证
     * 验证用户输入的账号和密码是否正确,错误抛出异常
     *
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        log.info("===============Shiro登录认证开始============");
        //TODO: 校验token
        String token = (String) authenticationToken.getCredentials();
        if (StringUtils.isEmpty(token)) {
            throw new AuthenticationException("token为空!");
        }
        //TODO:从token中取出用户名
        String username = jwtTokenUtil.getUserNameFromToken(token);
        if (org.apache.commons.lang3.StringUtils.isEmpty(username)) {
            throw new AuthenticationException("token非法无效!");
        }
        //TODO: 判断用户是否存在
        LoginUserVo loginUserVoVo = userService.selectLoginUserVoByLoginName(username);
        if (ObjectUtils.isEmpty(loginUserVoVo)) {
            throw new AuthenticationException("用户不存在!");
        }
        //TODO: 判断用户状态
        if (loginUserVoVo.getStatus() == 1) {
            throw new AuthenticationException("账号已被锁定,请联系管理员!");
        }

        //TODO:校验token是否超时失效
        if (!jwtTokenUtil.validateToken(token, username)) {
            throw new AuthenticationException("Token失效,请重新登录!");
        }
        log.info("===============Shiro登录认证成功============");
        return new SimpleAuthenticationInfo(loginUserVoVo, token, getName());
    }

    /**
     * 清除当前用户的权限认证缓存
     *
     * @param principals 权限信息
     */
    @Override
    public void clearCache(PrincipalCollection principals) {
        super.clearCache(principals);
    }
}

通过代码实现拦截

@Configuration
public class ShiroJwtConfig {

    @Resource(name = "shiroRedisTemplate")
    private RedisTemplate redisTemplate;

    /**
     * 全局缓存时间,单位为秒
     */
    @Value("${hdw.jwt.expiration}")
    private int cacheLive;

    /**
     * 全局缓存名称前缀,默认为应用名
     */
    @Value("${spring.application.name}")
    private String cacheKeyPrefix;

    @Bean
    public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    /**
     * Filter Chain定义说明
     * 

* 1、一个URL可以配置多个Filter,使用逗号分隔 * 2、当设置多个过滤器时,全部验证通过,才视为通过 * 3、部分过滤器可指定参数,如perms,roles */ @Bean("shiroFilter") public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); // 拦截器 Map filterChainDefinitionMap = new LinkedHashMap(); //TODO:配置不会被拦截的链接 顺序判断 filterChainDefinitionMap.put("/sys/captcha", "anon"); //登录验证码接口排除 filterChainDefinitionMap.put("/sys/login", "anon"); //登录接口排除 filterChainDefinitionMap.put("/sys/logout", "anon"); //登出接口排除 filterChainDefinitionMap.put("/sys/encrypt", "anon");//加密 filterChainDefinitionMap.put("/api/**", "anon");// API接口 //TODO:开放的静态资源 filterChainDefinitionMap.put("/favicon.ico", "anon");// 网站图标 filterChainDefinitionMap.put("/bootstrap/**", "anon"); filterChainDefinitionMap.put("/css/**", "anon"); filterChainDefinitionMap.put("/font/**", "anon"); filterChainDefinitionMap.put("/images/**", "anon"); filterChainDefinitionMap.put("/js/**", "anon"); filterChainDefinitionMap.put("/plugins/**", "anon"); filterChainDefinitionMap.put("/upload/**", "anon"); filterChainDefinitionMap.put("/qr/**", "anon"); filterChainDefinitionMap.put("/**/*.js", "anon"); filterChainDefinitionMap.put("/**/*.css", "anon"); filterChainDefinitionMap.put("/**/*.html", "anon"); filterChainDefinitionMap.put("/**/*.svg", "anon"); filterChainDefinitionMap.put("/**/*.pdf", "anon"); filterChainDefinitionMap.put("/**/*.jpg", "anon"); filterChainDefinitionMap.put("/**/*.png", "anon"); filterChainDefinitionMap.put("/**/*.ico", "anon"); //TODO:排除字体格式的后缀 filterChainDefinitionMap.put("/**/*.ttf", "anon"); filterChainDefinitionMap.put("/**/*.woff", "anon"); filterChainDefinitionMap.put("/**/*.woff2", "anon"); filterChainDefinitionMap.put("/druid/**", "anon"); filterChainDefinitionMap.put("/swagger-ui.html", "anon"); filterChainDefinitionMap.put("/swagger**/**", "anon"); filterChainDefinitionMap.put("/webjars/**", "anon"); filterChainDefinitionMap.put("/v2/**", "anon"); filterChainDefinitionMap.put("/doc.html", "anon"); //TODO:性能监控 filterChainDefinitionMap.put("/actuator/**", "anon"); //TODO:测试示例 filterChainDefinitionMap.put("/test/**", "anon"); //模板页面 //TODO:websocket排除 filterChainDefinitionMap.put("/ws/**", "anon"); //TODO:添加自己的过滤器并且取名为jwt Map filterMap = new HashMap(1); filterMap.put("jwt", new JwtFilter()); shiroFilterFactoryBean.setFilters(filterMap); //TODO:过滤链定义,从上向下顺序执行,一般将/**放在最为下边 filterChainDefinitionMap.put("/**", "jwt"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } /** * 安全管理器配置 * * @return */ @Bean public DefaultWebSecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); // 设置自定义缓存 securityManager.setCacheManager(shiroRedisCacheManager()); // 设置自定义realm securityManager.setRealm(jwtRealm()); securityManager.setSubjectDAO(subjectDAO()); // 设置自定义会话 securityManager.setSessionManager(sessionManager()); return securityManager; } //创建自定义realm @Bean public Realm jwtRealm() { JwtRealm jwtRealm = new JwtRealm(); jwtRealm.setCacheManager(shiroRedisCacheManager()); jwtRealm.setCachingEnabled(true); return jwtRealm; } /** * 缓存管理器 * * @return */ @Bean public CacheManager shiroRedisCacheManager() { ShiroRedisCacheManager redisCacheManager = new ShiroRedisCacheManager(cacheLive * 1000, cacheKeyPrefix + ":shiro-cache:", redisTemplate); return redisCacheManager; } /** * 会话管理器 * * @return */ @Bean public SessionManager sessionManager() { DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); sessionManager.setCacheManager(shiroRedisCacheManager()); /** * 会话验证 * Shiro提供了会话验证调度器,用于定期的验证会话是否已过期,如果过期将停止会话; * 出于性能考虑,一般情况下都是获取会话时来验证会话是否过期并停止会话的; * 但是如在web环境中,如果用户不主动退出是不知道会话是否过期的,因此需要定期的检测会话是否过期, * Shiro提供了会话验证调度器SessionValidationScheduler来做这件事情。 */ //TODO:单位为毫秒(1秒=1000毫秒)设置为7天 sessionManager.setSessionValidationInterval(1000 * cacheLive); //TODO: 设置全局session超时时间 sessionManager.setGlobalSessionTimeout(1000 * cacheLive); //TODO:删除过期session sessionManager.setDeleteInvalidSessions(true); //TODO: 开启/禁用绘画验证 sessionManager.setSessionValidationSchedulerEnabled(false); SimpleCookie cookie = new SimpleCookie(); //TODO:设置Cookie名字,默认为JSESSIONID; cookie.setName(cacheKeyPrefix + "-"); //TODO:设置Cookie的域名,默认空,即当前访问的域名; cookie.setDomain(""); //TODO:设置Cookie的路径,默认空,即存储在域名根下; cookie.setPath(""); //TODO:设置Cookie的过期时间,单位为秒,默认-1表示关闭浏览器时过期Cookie; cookie.setMaxAge(cacheLive); //TODO:如果设置为true,则客户端不会暴露给客户端脚本代码,使用HttpOnly cookie有助于减少某些类型的跨站点脚本攻击 cookie.setHttpOnly(true); //TODO:sessionManager创建会话Cookie的模板 sessionManager.setSessionIdCookie(cookie); //TODO:是否启用/禁用Session Id Cookie,默认是启用的;如果禁用后将不会设置Session Id Cookie,即默认使用了Servlet容器的JSESSIONID,且通过URL重写(URL中的“;JSESSIONID=id”部分)保存Session Id。 sessionManager.setSessionIdCookieEnabled(false); return sessionManager; } /** * 关闭Shiro自带的Session,详见文档 * http://shiro.apache.org/session-management.html#SessionManagement * * @return */ @Bean public DefaultSubjectDAO subjectDAO() { DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO(); DefaultSessionStorageEvaluator sessionStorageEvaluator = new DefaultSessionStorageEvaluator(); sessionStorageEvaluator.setSessionStorageEnabled(false); subjectDAO.setSessionStorageEvaluator(sessionStorageEvaluator); return subjectDAO; } /** * 下面的代码是添加注解支持 * * @return */ @Bean @DependsOn("lifecycleBeanPostProcessor") public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); defaultAdvisorAutoProxyCreator.setProxyTargetClass(true); return defaultAdvisorAutoProxyCreator; } @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) { AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); advisor.setSecurityManager(securityManager); return advisor; } }

以上拦截认证授权的具体业务场景可以从各自的实际情况进行修改

你可能感兴趣的:(#,认证及拦截部分,后端,系统安全,web安全,安全架构)