shiro权限入门(二)

这个是用的自带的登录验证:shiro权限入门(一)

这次又搞了一个基于token的,没有使用redis缓存,用了一个ehcache缓存

直接使用上面的mp项目框架加了一个模块

思路:

① 登录调用登录接口,生成token,将权限,token等返回,并存入缓存

② 登录之后调用其他任意接口,首先会经过shiroFilter,然后调用shiroRealm认证,在这里面只需要判断缓存中有没有token

 这一步相当于登录拦截器了

③ 如果接口有权限注解,此时会调用shiroRealm的授权

④ 退出登录,调用shiro的登出方法,清除ehcache缓存

1.登录方法

入参方式和反参方式根据项目调整

@Service
public class UserServiceImpl extends ServiceImpl<UserDao, User> implements UserService {

    @Autowired
    private CacheManager cacheManager;

    @Override
    public Map auth(UserForm record) {
        // 更安全点是加公钥私钥,加图片验证码等手段
        // 1.得到传进来的账号密码
        String password = record.getPassword();
        String userCode = record.getUserCode();
        // 2.根据用户账号查询
        QueryWrapper<User> queryWrapper = new QueryWrapper<User>();
        queryWrapper.eq("user_code", userCode);
        List<User> users = baseMapper.selectList(queryWrapper);
        if (null == users || users.size() != 1) {
            // 此处应该为自定义异常类
            throw new IncorrectCredentialsException("账号不存在");
        }
        User user = users.get(0);
        // 在此处可以考虑查询a表中五分钟之内的改账号的条数
        // 2.对传进来的密码加密处理
        String s = new Sha256Hash(password, user.getSalt(), 2).toHex();
        if (!s.equals(user.getPassword())) {
            // 这个地方可以考虑做一个失败三次之后冻结账号,防止暴力破解
            // 想法一:每次判断密码不对时,在这个地方对a表插入一条记录;
            // 在第二步上面进行一个对a表的查询,查询当前时间五分钟之内,该账号的条数,如果大于3条,就认为冻结
            // 想法二:直接该账号状态改为冻结,除非管理员手动解除
            // 此处应该为自定义异常类
            throw new IncorrectCredentialsException("账号密码错误");
        }
        // 3.校验离职或停用等状态
        if (user.getUserStatus().equals(3)) {
            // 此处应该为自定义异常类
            throw new IncorrectCredentialsException("账号停用");
        }
        // 4.生成token,并存入缓存
        String token = UUID.randomUUID().toString();
        EHCacheUtils.setCache(cacheManager, token, user);
        Map map = new HashMap();
        map.put("token", token);
        map.put("meunList", null); // TODO 此处返回该账号的菜单权限
        map.put("buttonList", null); // TODO  此处返回该账号的按钮权限
        return map;
    }

    @Override
    public void logout(HttpServletRequest request) {
        String token = request.getHeader("token");
        if (!StringUtils.isEmpty(token)) {
            EHCacheUtils.removeCache(cacheManager, token);
        }
        SecurityUtils.getSubject().logout();
    }

}

2.shiroConfig类

@Configuration
public class ShiroConfig {

    //将自己的验证方式加入容器
    @Bean
    public ShiroRealm initRealm() {
        ShiroRealm realm = new ShiroRealm();
        realm.setAuthenticationCacheName("authorization");
        return realm;
    }

    //权限管理,配置主要是Realm的管理认证
    @Bean(name = "securityManager")
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        EhCacheManager ehCacheManager = new EhCacheManager();
        ehCacheManager.setCacheManager(initEhCacheMangerFactory().getObject());
        securityManager.setCacheManager(ehCacheManager);
        securityManager.setRealm(initRealm());
        return securityManager;
    }

    @Bean(name = "ehCacheManager")
    public EhCacheManagerFactoryBean initEhCacheMangerFactory() {
        EhCacheManagerFactoryBean ehCacheFactoryBean = new EhCacheManagerFactoryBean();
        ehCacheFactoryBean.setShared(true);
        ResourceLoader resourceLoader = new DefaultResourceLoader();
        ehCacheFactoryBean.setConfigLocation(resourceLoader.getResource("classpath:/ehcache.xml"));
        return ehCacheFactoryBean;
    }

    @Bean(name = "cacheManager")
    public EhCacheCacheManager getCacheManager(EhCacheManagerFactoryBean ehCacheManager) {

        EhCacheCacheManager cacheManager = new EhCacheCacheManager();
        cacheManager.setCacheManager(ehCacheManager.getObject());
        return cacheManager;
    }


    //Filter工厂,设置对应的过滤条件和跳转条件
    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager") SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        //权限设置, 这里有拦截顺序判断,不能用hashMap
        Map<String, String> map = new LinkedHashMap<>();
        map.put("/static/**", "anon");
        map.put("/login/*", "anon");
        map.put("/error", "anon");
        //<!-- 过滤链定义,从上向下顺序执行,一般将/**放在最为下边 -->
        map.put("/**", "authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
        Map<String, Filter> filterMap = new HashMap<>();
        filterMap.put("authc", new ShiroFilter());
        //登出
        LogoutFilter logoutFilter = new LogoutFilter();
        filterMap.put("logout", logoutFilter);
        shiroFilterFactoryBean.setFilters(filterMap);
        return shiroFilterFactoryBean;
    }


    @Bean
    public FilterRegistrationBean filterRegistrationBean() {
        FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
        DelegatingFilterProxy proxy = new DelegatingFilterProxy();
        proxy.setTargetFilterLifecycle(true);
        proxy.setTargetBeanName("shiroFilter");
        filterRegistration.setFilter(proxy);
        return filterRegistration;
    }

    // 开启@RequiresPermissions()权限注解
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
}

3.shiroFilter类

其中这个里面两个方式是权限不足会调用,一个是token过期会调用,返回方式根据项目修改

public class ShiroFilter extends AuthenticatingFilter {

    @Override
    protected AuthenticationToken createToken(ServletRequest request,
                                              ServletResponse response) throws Exception {
        //获取请求token
        String token = getRequestToken((HttpServletRequest) request);
        if (StringUtils.isBlank(token)) {
            return null;
        }
        return new ShiroToken(token);
    }

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response,
                                      Object mappedValue) {
        if (((HttpServletRequest) request).getMethod().equals(RequestMethod.OPTIONS.name())) {
            return true;
        }

        return false;
    }

    @Override
    protected boolean onAccessDenied(ServletRequest request,
                                     ServletResponse response) throws Exception {
        //获取请求token,如果token不存在,直接返回
        String token = getRequestToken((HttpServletRequest) request);
        if (StringUtils.isBlank(token)) {
            HttpServletResponse httpResponse = (HttpServletResponse) response;
            httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
            PrintWriter out = null;
            try {
                httpResponse.setCharacterEncoding("UTF-8");
                httpResponse.setContentType("application/json");
                out = response.getWriter();
                Map map = new HashMap();
                map.put("status",0);
                map.put("errorMsg","登录超时,请重新登录");
                out.println(map);
            } catch (Exception e) {
            } finally {
                if (null != out) {
                    out.flush();
                    out.close();
                }
            }

            return false;
        }

        return executeLogin(request, response);
    }

    @Override
    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e,
                                     ServletRequest request, ServletResponse response) {
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
        httpResponse.setCharacterEncoding("UTF-8");
        httpResponse.setContentType("application/json");
        PrintWriter out = null;
        try {
            out = response.getWriter();
            Map map = new HashMap();
            map.put("status",0);
            map.put("errorMsg","请重新登录");
            out.println(map);
        } catch (Exception e1) {
        } finally {
            if (null != out) {
                out.flush();
                out.close();
            }
        }

        return false;
    }

    private String getRequestToken(HttpServletRequest httpRequest) throws Exception {
        //从header中获取token
        String token = httpRequest.getHeader("token");
        //如果header中不存在token,则从参数中获取token
        if (StringUtils.isBlank(token)) {
            token = httpRequest.getParameter("token");
        }
        return token;
    }

}

4.shiroRealm类

@Component
public class ShiroRealm extends AuthorizingRealm {

    @Autowired
    private CacheManager cacheManager;

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

    /**
     * 授权(验证权限时调用)
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        User user = (User)principals.getPrimaryPrincipal();
        Long userId = user.getUserId();
        // 用户权限列表
        // Set<String> permsSet = shiroService.getUserPermissions(userId);
        // 模拟数据,接口上加上注解即可
        Set<String> permsSet = new LinkedHashSet<>();
        permsSet.add("user:add");
        permsSet.add("user:list");
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.setStringPermissions(permsSet);
        return info;
    }

    /**
     * 认证(登录时调用)
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String accessToken = (String) token.getPrincipal();
        if(accessToken == null){
            throw new IncorrectCredentialsException("token失效,请重新登录");
        }
        User user = (User)EHCacheUtils.getCache(cacheManager, accessToken);
        if(null == user){
            throw new IncorrectCredentialsException("超时 请重新登录");
        }
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, accessToken, getName());
        return info;
    }
}

5.shiroToken类

public class ShiroToken implements AuthenticationToken {

    private String token; // 存放token 前后端用

    public ShiroToken(String token) {
        this.token = token;
    }

    @Override
    public String getPrincipal() {
        return token;
    }

    @Override
    public Object getCredentials() {
        return token;
    }
}

5.ehcache.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">

    <diskStore path="${java.io.tmpdir}/${system.project_name}/cache"/>

    <!-- 
        配置自定义缓存
        maxElementsInMemory:缓存中允许创建的最大对象数
        eternal:缓存中对象是否为永久的,如果是,超时设置将被忽略,对象从不过期。
        timeToIdleSeconds:缓存数据的钝化时间,也就是在一个元素消亡之前,
                    两次访问时间的最大时间间隔值,这只能在元素不是永久驻留时有效,
                    如果该值是 0 就意味着元素可以停顿无穷长的时间。
        timeToLiveSeconds:缓存数据的生存时间,也就是一个元素从构建到消亡的最大时间间隔值,
                    这只能在元素不是永久驻留时有效,如果该值是0就意味着元素可以停顿无穷长的时间。
        overflowToDisk:内存不足时,是否启用磁盘缓存。
        memoryStoreEvictionPolicy:缓存满了之后的淘汰算法。
    -->
    <defaultCache
            maxElementsInMemory="10000"
            maxEntriesLocalHeap="2000"
            maxElementsOnDisk="0"
            eternal="true"
            overflowToDisk="true"
            diskPersistent="false"
            timeToIdleSeconds="0"
            timeToLiveSeconds="0"
            diskSpoolBufferSizeMB="50"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LFU"
    />
   
    <cache name="authorization" maxElementsInMemory="100" maxEntriesLocalHeap="2000" timeToLiveSeconds="3600"
           eternal="false" overflowToDisk="false"/>
    

    <!--登录存放用户id
    timeToIdleSeconds:缓存创建以后,最后一次访问缓存的日期至失效之时的时间间隔
    timeToLiveSeconds:缓存自创建日期起至失效时的间隔时间
    失效时间1小时
    -->
    <cache name="userIdCache"
           maxEntriesLocalHeap="2000"
           eternal="false"
           timeToLiveSeconds="3600"
           overflowToDisk="false"
           statistics="true"/>
</ehcache>

6.ehcacheUtils工具类

public class EHCacheUtils {

    private static final String USER_CACHE = "userIdCache";
    /**
     * 设置缓存对象
     * @param cacheManager
     * @param key
     * @param object
     */
    public static void setCache(CacheManager cacheManager, String key, Object object){
        Cache cache = cacheManager.getCache(USER_CACHE);
        Element element = new Element(key,object);
        cache.put(element);
    }

    /**
     * 从缓存中取出对象
     * @param cacheManager
     * @param key
     * @return
     */
    public static Object getCache(CacheManager cacheManager, String key){
        Object object = null;
        Cache cache = cacheManager.getCache(USER_CACHE);
        if(cache.get(key)!=null && !cache.get(key).equals("")){
            object = cache.get(key).getObjectValue();
        }
        return object;

    }

    /**
     * 从缓存中删除对象
     * @param cacheManager
     * @param key
     */
    public static void removeCache(CacheManager cacheManager, String key){
        Cache cache = cacheManager.getCache(USER_CACHE);
        cache.remove(key);
    }
}

注意事项:

pom中加入

<dependency>
   <groupId>net.sf.ehcache</groupId>
   <artifactId>ehcache</artifactId>
   <version>2.10.1</version>
</dependency>

如果

@RequiresPermissions("user:list") 注解不起作用有可能是缺少aop

加入

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值