集成shiro

基础概念

1、UsernamePasswordToken,用来封装用户登录信息
    public UsernamePasswordToken(final String username, final char[] password,
                                 final boolean rememberMe, final String host) {
        this.username = username;
        this.password = password;
        this.rememberMe = rememberMe;
        this.host = host;
    }
2、 SecurityManager,Shiro 的核心部分,负责安全认证与授权。

3、Subject,Shiro 的一个抽象概念,包含了用户信息
   Subject currentUser = SecurityUtils.getSubject();
        if (currentUser.isAuthenticated()) {
            UserBO userBO = (UserBO) currentUser.getPrincipal();
            if (null != userBO) {
                return userBO;
            }
        }

4、Realm,根据项目的需求,验证doGetAuthorizationInfo和授权doGetAuthenticationInfo的逻辑在 Realm 中实现。

5、AuthenticationInfo,用户的角色信息集合,认证时使用。
   new SimpleAuthenticationInfo(user, user.getPassword(), getName());

6、AuthorizationInfo,角色的权限信息集合(当前用户有哪些角色,可以访问哪个uri),授权时使用。

7、DefaultWebSecurityManager,安全管理器,开发者自定义的 Realm 需要注入到 DefaultWebSecurityManager 进行管理才能生效
    @Bean("customSecurityManager")
    DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager defaultSecurityManager = new DefaultWebSecurityManager();
        defaultSecurityManager.setRealm(realm());
        defaultSecurityManager.setSessionManager(sessionManager());
        return defaultSecurityManager;
    }

8、ShiroFilterFactoryBean,Shiro 的基本运行机制是开发者定制规则(Filter)Shiro去执行
    shiroFilterFactoryBean.setFilterChainDefinitionMap( shiroService.loadFilterChainDefinitionMap());

首先shiro独立于容器,因为需要引用容器内部的类,所以先将shiro对应的配置类加入到容器,具体实现ApplicationContextAware接口,重写方法

 @Override
 public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        Realm userRealm = applicationContext.getBean(WesRealm.class);
        DefaultWebSecurityManager defaultWebSecurityManager = (DefaultWebSecurityManager) applicationContext.getBean(SecurityManager.class);
        defaultWebSecurityManager.setRealm(userRealm);
 }

shiro如何获取当前用户信息和验证,这就用到了我们的realm,简单的realm只是给类,要想被shiro这个大家庭接受,需要“自降”身价,认AuthorizingRealm为父(这也是普通类的福分了,毕竟地位一下子不一样了)

获取当前用户的角色和资源

    /**
     * 获取当前用户有哪些角色给SimpleAuthorizationInfo的roles
     * 获取当前用户可访问哪些资源给SimpleAuthorizationInfo的stringPermissions
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        if (principals == null) {
            throw new AuthorizationException("PrincipalCollection method argument cannot be null.");
        }

       if (principals.getPrimaryPrincipal() instanceof UserBO) {
            UserBO userBO = (UserBO) principals.getPrimaryPrincipal();
            List<String> userUriList = userService.queryUrlListByUserId(userBO.getUserID());
            SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
            List<String> roleIdList = userService.getRoleIdListByUserId(userBO.getUserID());
            if (CollectionUtil.isNotEmpty(roleIdList)) {
                authorizationInfo.addRoles(roleIdList);
            }
            for (String uri : userUriList) {
                authorizationInfo.addStringPermission(uri);
            }
            return authorizationInfo;
        }
        return null;
    }

验证当前用户密码等

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //该参数是是登录时UsernamePasswordToken的值
        WesUserToken wesUserToken = (WesUserToken) authenticationToken;
        if (wesUserToken.getUsername() == null) {
            throw new AccountException("Null usernames are not allowed by this realm.");
        }
        //可判断用户是否存在,密码是否正确
        UserBO user = userService.getUserWithTokenByName(wesUserToken.getUsername());
        if (user == null) {
            throw new UnknownAccountException("No account found for admin [" + wesUserToken.getUsername() + "]");
        }
        //将user赋值给SimpleAuthenticationInfo,方便权限判断时获取,要不怎么知道当前是谁
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user, user.getPassword(), getName());
        if (user.getSalt() != null) {
            //加盐,这块根据项目需要
            authenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(user.getSalt()));
        }
        return authenticationInfo;
    }

具体看下AuthenticationToken参数

public class WesUserToken extends UsernamePasswordToken {
    //登录时赋值currentUser.login(new WesUserToken(userBO.getUserName(), userBO.getPassword()));
    public WesUserToken(final String username, final String password) {
        super(username, password.toCharArray(), false, null);
    }
}

当前realm还是比较独立的,没有归为shiro,如何处理呢?再shiro的配置类中,将其设为bean并绑给DefaultWebSecurityManager

    
    @Bean("customSecurityManager")
    DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager defaultSecurityManager = new DefaultWebSecurityManager();
        defaultSecurityManager.setRealm(realm());
        return defaultSecurityManager;
    }

    @Bean
    public Realm realm() {
        WesRealm realm = new WesRealm();
        return realm;
    }

这样用户的信息补充完了,具体整个系统我们要拦截哪些资源呐?配置filter

    /**
     * 以下参数也可以new,我习惯使用bean
     * @param shiroService 自己的具体校验哪些url的配置及其更新
     * @param tokenCheckFilter 自定义的过滤规则
     * @param securityManager 
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactory(
            @Qualifier("shiroService") ShiroService shiroService,
            @Qualifier("tokenCheckFilter") TokenCheckFilter tokenCheckFilter,
            @Qualifier("customSecurityManager") SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        Map<String, Filter> filtersMap = new LinkedHashMap<>();
        // 定义过滤器名称,注意这个"token"后面要考 【注:map里面key值对于的value要为authc才能使用自定义的过滤器】
        filtersMap.put("token", tokenCheckFilter);
        //filtersMap.put("corn", myFormAuthenticationFilter);
        shiroFilterFactoryBean.setFilters(filtersMap);
        //登录url,这个前后端分离其实是不需要,具体后面再说
        shiroFilterFactoryBean.setLoginUrl("/wes/passport/account/unlogin");
        shiroFilterFactoryBean.setFilterChainDefinitionMap( shiroService.loadFilterChainDefinitionMap());
        return shiroFilterFactoryBean;
    }

    @Bean
    public ShiroService shiroService() {
        return new ShiroServiceImpl();
    }

    @Bean
    public TokenCheckFilter tokenCheckFilter(){
        return new TokenCheckFilter();
    }

具体的拦截什么?

    @Override
    public Map<String, String> loadFilterChainDefinitionMap(){
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        List<AuthView> list = authViewService.getAll();
        if (CollectionUtil.isNotEmpty(list)) {

            Map<Integer, List<AuthView>> viewTypeMap = list.stream().collect(Collectors.groupingBy(AuthView::getType));

            List<String> buttonList = viewTypeMap.getOrDefault(2, new ArrayList<>()).stream().map(AuthView::getUrl).collect(Collectors.toList());
            for (String viewUrl : buttonList) {
                String[] urlNum = viewUrl.split(StrUtil.COLON);
                filterChainDefinitionMap.put(urlNum[0], "authc,token");
            }

           /…………逻辑同上,注意这个filterChainDefinitionMap.put的先后顺序,先上后下
        }
        filterChainDefinitionMap.put("/**", "anon");
        return filterChainDefinitionMap;
    }

如何拦截?看我们“token” filtersMap.put("token", tokenCheckFilter);经测试要继承FormAuthenticationFilter哦,这块跟具体的业务逻辑有很强的关系


    /**
     * 判断是否拥有权限 true:认证成功  false:认证失败
     * mappedValue 访问该url时需要的权限
     * subject.isPermitted 判断访问的用户是否拥有mappedValue权限
     */
    @Override
    public boolean isAccessAllowed(ServletRequest httpRequest, ServletResponse response, Object mappedValue) {

        HttpServletRequest request = (HttpServletRequest) httpRequest;
        String requestUri = request.getRequestURI();
        requestUri = requestUri.replaceAll(DOUBLE_SLASH, SINGLE_SLASH);

        UserTokenCheck paramUser = getInfoFromCookie(request.getCookies());
        log.info("拦截器 当前{} user{}", requestUri, paramUser);
        if (interceptorUtil.checkParamSimple(paramUser)) {
            interceptorUtil.login(paramUser.getUserId());
            return true;
        }

        List<AuthView> list = authViewService.getAll();
        if (CollectionUtil.isNotEmpty(list)) {

            Map<Integer, List<AuthView>> viewTypeMap = list.stream().collect(Collectors.groupingBy(AuthView::getType));
            List<String> allowList = viewTypeMap.getOrDefault(0, new ArrayList<>()).stream().map(AuthView::getUrl).collect(Collectors.toList());
            if (CollectionUtil.isNotEmpty(allowList) && interceptorUtil.checkContainsUri(requestUri, allowList)) {
                interceptorUtil.login(paramUser.getUserId());
                return true;
            }
        return true;
    }

当验证失败是,覆盖父类发方法,该部分与业务强相关,由于历史原因以下处理方式只能流泪,注释调的那套处理方式是比较推荐滴

 @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
        Subject subject = getSubject(request, response);
        // If the subject isn't identified, redirect to login URL
        if (subject.getPrincipal() == null) {
            //跳转至登录页
            //saveRequestAndRedirectToLogin(request, response);
            throw ErrorCodes.ERROR_COOKIE_MISS.exception();
        } else {
            //给前端提示无接口访问权限的错误码
            //saveRequestAndReturnApiAccessError(request, response);
            throw ErrorCodes.ERROR_ROLE_NOT_RIGHT.exception();
        }
        //return true;
    }

    @Override
    protected void redirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
//        JSONObject jsonObject = new JSONObject();
//        jsonObject.put("returnCode", "1000005");
//        jsonObject.put("returnUserMsg", "登陆时间过长,请重新登陆");
//        try {
//            flushMsgStrToClient(response, jsonObject);
//        } catch (ServletException e) {
//            e.printStackTrace();
//        }
        throw ErrorCodes.ERROR_ROLE_NOT_RIGHT.exception();
    }

    public static void flushMsgStrToClient(ServletResponse response, Object object)
            throws IOException, ServletException {
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(JSONObject.toJSONString(object));
        response.getWriter().flush();
    }

    protected void saveRequestAndReturnApiAccessError(ServletRequest request, ServletResponse response) {
        saveRequest(request);
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("returnCode", "1000001");
        jsonObject.put("returnUserMsg", "无权限请求对应api接口");
        try {
            flushMsgStrToClient(response, jsonObject);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

先这样done

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值