zoukankan      html  css  js  c++  java
  • 基于Shiro的登录功能 设计思路

    认证流程

    Shiro的认证流程可以看作是个“有窗户黑盒”,

    整个流程都有框架控制,对外的入口只有subject.login(token);,这代表“黑盒”

    流程中的每一个组件,都可以使用Spring IOC容器里的Bean来替换,以实现拓展化、个性化,这代表“有窗户”。

    本示例的认证流程可以参考下图:

    (黑色区域中的红箭头线只代表受关注组件的流程,而不是直接调用,实际上Shiro每个流程的组件都是互相解耦的)

    黑色区域里的两块白色区域,就是两个自定义的类。

    关键代码如下:

        /**
         * 认证
         */
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)
                throws AuthenticationException {
            UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
            String username = token.getUsername();
            AccountPo po = accountMapper.getByUsername(username);
            if (po == null) {
                throw new UnknownAccountException("用户名或密码错误。");
            }
            if (!po.getEnable_flag()) {
                throw new DisabledAccountException("该用户已被禁用。");
            }
            CredentialsDto credentials = new CredentialsDto(po.getPassword(), po.getSalt());
            return new SimpleAuthenticationInfo(username, credentials, getName());
        }
        /**
         * 密码匹配
         */
        @Override
        public boolean doCredentialsMatch(AuthenticationToken authenticationToken, AuthenticationInfo info) {
            UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
            String password = new String(token.getPassword());
            CredentialsDto credentials = (CredentialsDto) info.getCredentials();
            String salt = credentials.getSalt();
            String passwordMd5 = DigestUtils.md5Hex(password + salt);
            boolean result = equals(passwordMd5, credentials.getPasswordMd5());
            if (!result) {
                throw new IncorrectCredentialsException("用户名或密码错误。");
            }
            return true;
        }

    认证过滤

    直接在spring-shiro.xml的shiroFilter.filterChainDefinitions中定义哪些URL需要认证才能访问并不利于维护,

    更合适的方法可能是写一个读取器,告诉filterChainDefinitions哪些URL不需要认证即可,需要认证的写在最后

        <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
            <property name="securityManager" ref="securityManager" />
            <property name="loginUrl" value="/" />
            <property name="unauthorizedUrl" value="/html/unauthorized.html" />
            <property name="filterChainDefinitions">
                <value>
                    #{filterChainDefinitionsLoader.anonUrls()}
                    /**=authc
                </value>
            </property>
        </bean>
    /**
     * Shiro的FilterChainDefinitions配置加载器
     *
     * @author Deolin
     */
    public class FilterChainDefinitionsLoader {
    
        @Autowired
        private UnauthenticationMapper unauthenticationMapper;
    
        /**
         * 从数据库`unauthorization`表中读取所有匿名URL
         *
         * @return FilterChainDefinitions内容片段
         */
        public String anonUrls() {
            StringBuilder sb = new StringBuilder(400);
            List<String> urls = authenticationMapper.listUrls();
            for (String url : urls) {
                sb.append(url);
                sb.append("=anon");
                sb.append(CRLF);
            }
            return sb.toString();
        }
    
    }

    FilterChainDefinitionsLoader.anonUrls()会在项目启动时调用,读取到所有匿名权限URL,

    拼接成字符串,返回给filterChainDefinitions

    授权配置

    采用基于角色的权限设计,一个权限对应一个URL。一个用户间接地拥有多个权限,间接地能够访问多个权限所一一对应的URL。

    这样一来,权限会全部配置在spring-shiro.xml的shiroFilter.filterChainDefinitions中,例如:

    /html/students-view.html=perms["students-view"]

    /student/add=perms["studentAdd"]

    /student/list=perms["studentList"]

    红蓝部分都是可自定义的,项目中每个涉及到权限的URL,都应该定义,

    并保存到数据库`permission`表的name字段和url字段。

    即`permission`是张字典表,项目中有多少个涉及到权限的URL,表中就会有多少字段。

    授权配置也需要读取器

            <property name="filterChainDefinitions">
                <value>
                    #{filterChainDefinitionsLoader.anonUrls()}
                    #{filterChainDefinitionsLoader.permsUrls()}
                    /**=authc
                </value>
            </property>
        /**
         * 从数据库`permission`表中读取所有权限
         *
         * @return FilterChainDefinitions内容片段
         */
        public String permsUrls() {
            StringBuilder sb = new StringBuilder(400);
            List<PermissionPo> pos = permissionMapper.list();
            for (PermissionPo po : pos) {
                sb.append(po.getUrl());
                sb.append("=perms["");
                sb.append(po.getName());
                sb.append(""]");
                sb.append(CRLF);
            }
            return sb.toString();
        }

    授权流程

    关键代码如下:

        /**
         * 授权
         */
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
            String username = (String) principals.getPrimaryPrincipal();
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
            Set<String> roleNames = accountMapper.listRoleNames(username);
            Set<String> permissionNames = accountMapper.listPermissionNames(username);
            info.setRoles(roleNames);
            info.setStringPermissions(permissionNames);
            return info;
        }

    基于过滤器链的认证、权限检查

    shiro的filterChainDefinitions是在项目启动是加载的,所以可以通过spel表达式,调用bean的方法,将各种字典表中的URL,拼接起来,返回给filterChainDefinitions,解释为“xxxxURL需要认证才能访问”“xxURL需要yyy的权限才能访问”。

    这里需要事先设计好字典表。

    自定义过滤器

    shiro的自带过滤器,似乎无法处理AJAX请求,至少无法返回项目通用JSON数据结构,比如自带的org.apache.shiro.web.filter.authc.AuthenticationFilter类和org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter类(它们分别被过滤器链中的authc标识和perms标识所映射),它们各自的onAccessDenied方法,只用重定向处理,而没有返回JSON的处理,所以,需要专门写一些自定义过滤器,以及用于处理AJAX请求的工具类。

    /**
     * 为shiro的自定义Filter准备的工具类<br>
     * <br>
     * 用于判断请求是否是AJAX请求和输出JSON
     *
     * @author Deolin
     */
    public class FilterUtil {
    
        public static boolean isAJAX(ServletRequest request) {
            return "XMLHttpRequest".equalsIgnoreCase(((HttpServletRequest) request).getHeader("X-Requested-With"));
        }
    
        public static void out(ServletResponse response, RequestResult requestResult) {
            ObjectMapper jackson = new ObjectMapper();
            PrintWriter out = null;
            try {
                response.setCharacterEncoding("UTF-8");
                out = response.getWriter();
                out.println(jackson.writeValueAsString(requestResult));
            } catch (Exception e) {} finally {
                if (null != out) {
                    out.flush();
                    out.close();
                }
            }
        }
    
    }
    /**
     * 自定义AccessControlFilter<br>
     * <br>
     * 这个类会在spring-shiro.xml注册为looseAuthc,作为filterChainDefinitions的一个自定义标记,未通过过滤的页面请求会重定向到登录页面,未通过过滤的AJAX请求会返回JSON
     *
     * @author Deolin
     */
    public class LooseAuthcFilter extends AccessControlFilter {
    
        @Override
        protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue)
                throws Exception {
            Subject subject = getSubject(request, response);
            // “记住我”自动登录 或 输入用户名+密码手动登录
            if (subject.isRemembered() || subject.isAuthenticated()) {
                return true;
            } else {
                return false;
            }
        }
    
        // 如果isAccessAllowed返回false,则调用这个方法
        @Override
        protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
            if (FilterUtil.isAJAX(request)) {
                // 返回JSON对象,前端根据resp.result=-2跳转到登录页面
                FilterUtil.out(response, RequestResult.unauthenticate());
            } else {
                // 重定向到登录页面
                saveRequestAndRedirectToLogin(request, response);
            }
            return false;
        }
    
    }

    项目下载

    https://github.com/spldeolin/login-demo

  • 相关阅读:
    浅谈Java中的对象和对象引用
    学习Python要知道哪些重要的库和工具
    Python新手最容易犯的十大错误
    java截取字符串中的数字
    Android 多语言
    R语言学习笔记(二): 类与泛型函数
    R语言学习笔记(一):mode, class, typeof的区别
    代理实现方式
    如何将链表反转
    同步锁之lock
  • 原文地址:https://www.cnblogs.com/deolin/p/7637698.html
Copyright © 2011-2022 走看看