han_shiro

shiro

shiro框架能做什么

  • 认证:验证用户身份
  • 授权:对用户执行访问控制:判断用户是否被允许做某事
  • 会话管理:在任何环境下使用Session API,即使没有web
  • 加密:以前简洁 易用的方式使用加密功能,保护或隐藏数据防止被偷窥
  • Realms:聚集一个或者多个用户安全数据的数据源
  • 单点登录

shiro的四大核心部分

  • Authentication:身份验证
  • Authorization:授权,指访问过程中决定是否有权限去访问受保护的资源
  • Session Management:会话管理,管理用户特定的会话
  • Cryptography:加密

shiro的三个核心组件

  • Subject:正在与系统交互的用户,或者某个第三方服务。所有Subject实例都被绑定到SecurityManager上
  • SecurityManager:Shiro架构的心脏协调内部各个安全组件,管理内部组件实例,并通过它来提供安全管理的各种服务。当shiro与一个subject进行交互时,实质上是securitymanager处理所有繁重的的subject安全操作。

在ssm框架中

  • pom文件有关依赖
		<dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-all</artifactId>
            <version>1.3.2</version>
        </dependency>

  • web.xml中有关配置信息
<filter>
	<filter-name>shiroFilter</filter-name>
	<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>

	<init-param>
          <param-name>targetFilterLifecycle</param-name>
          <param-value>true</param-value>
    </init-param>
	<filter-mapping>
        <filter-name>shiroFilter</filter-name>
        <url-pattern>/*
	

  • 与spring整合xml
<!-- 配置 Bean 后置处理器: 会自动的调用和 Spring 整合后各个组件的生命周期方法. --><bean id="lifecycleBeanPostProcessor" 
        class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/><bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
	depends-on="lifecycleBeanPostProcessor"/><bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager"/></bean><!-- 包含shiro的配置文件 -->
          <import resource="classpath:applicationContext-shiro.xml"/>

  • shiro本身的xml(若按照上一步则该文件名为:applicationContext-shiro.xml)
<!-- 配置緩存管理器 -->
    <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
        <!-- 指定 ehcache 的配置文件,下面会给到 -->
        <property name="cacheManagerConfigFile" value="classpath:ehcache-shiro.xml"/>
    </bean>
    <!-- Realm 的配置(自己创建的类)  init-methood是初始化的方法-->
    <bean id="userAuthorizingRealm" class="com.ken.wms.security.realms.UserAuthorizingRealm" init-method="setCredentialMatcher"/>
 <!-- 认证器 -->  
    <bean id="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
        <property name="authenticationStrategy">
            <bean class="org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy"/>
        </property>
    </bean><!-- 配置 Shiro 的 SecurityManager Bean. -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="cacheManager" ref="cacheManager"/>
        <property name="authenticator" ref="authenticator"/>
        <property name="realms">
            <list>
                <ref bean="userAuthorizingRealm"/>
            </list>
        </property>
    </bean>
      
 <!-- 配置 ShiroFilter bean: 该 bean 的 id 必须和 web.xml 文件中配置的 shiro filter 的 name 一致  -->   
 	<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
 		 <!-- 装配 securityManager -->
        <property name="securityManager" ref="securityManager"/>
         <!-- 配置登陆页面 -->
        <property name="loginUrl" value="/index.jsp"/>
        <!-- 登陆成功后的一面 -->
        <property name="successUrl" value="/shiro-success.jsp"/>
        <!--用户访问无权限的链接时跳转此页面 -->
        <property name="unauthorizedUrl" value="/shiro-unauthorized.jsp"/>
        <property name="filters">
            <util:map>
                <entry key="roles" value-ref="anyOfRoles"/>
                <entry key="authc" value-ref="extendFormAuthenticationFilter"/>
            </util:map>
        </property>
        <!-- 具体配置需要拦截哪些 URL, 以及访问对应的 URL 时使用 Shiro 的什么 Filter 进行拦截.  -->
        <property name="filterChainDefinitionMap" ref="filterChainDefinitionMap"/>
    </bean>
     <!-- 配置身份验证器,处理 Ajax 请求 -->
    <bean id="extendFormAuthenticationFilter" class="com.ken.wms.security.filter.ExtendFormAuthenticationFilter">
        <property name="usernameParam" value="username"/>
        <property name="passwordParam" value="password"/>
        <property name="rememberMeParam" value="rememberMe"/>
        <property name="loginUrl" value="/login"/>
    </bean>
    <bean id="anyOfRoles" class="com.ken.wms.security.filter.AnyOfRolesAuthorizationFilter"/>
     <!-- 配置获取 URL 权限信息的 Factory -->
    <bean id="filterChainDefinitionMap" factory-bean="filterChainDefinitionMapBuilder"
          factory-method="builderFilterChainDefinitionMap"/>
    <bean id="filterChainDefinitionMapBuilder"
          class="com.ken.wms.security.service.FilterChainDefinitionMapBuilder"/>

缓存配置文件:ehcache-shiro.xml

<ehcache>
	 <diskStore path="java.io.tmpdir"/>
	 <defaultCache
        maxElementsInMemory="10000"
        eternal="false"
        timeToIdleSeconds="120"
        timeToLiveSeconds="120"
        overflowToDisk="true"
        />
        <cache name="sampleCache1"
        maxElementsInMemory="10000"
        eternal="false"
        timeToIdleSeconds="300"
        timeToLiveSeconds="600"
        overflowToDisk="true"
        />
        <cache name="sampleCache2"
        maxElementsInMemory="1000"
        eternal="true"
        timeToIdleSeconds="0"
        timeToLiveSeconds="0"
        overflowToDisk="false"
        /> </ehcache>

在Java中

  • 创建FilterChainDefinitionMapBuilder
package com.ken.wms.security.service;import com.ken.wms.dao.RolePermissionMapper;import com.ken.wms.domain.RolePermissionDO;import org.springframework.beans.factory.annotation.Autowired;import java.util.LinkedHashMap;import java.util.List;/**
 * 获取 URL 权限信息工厂类
 * @author ken
 * @since 2017/2/26.
 */public class FilterChainDefinitionMapBuilder {
    @Autowired
    private RolePermissionMapper rolePermissionMapper;
    private StringBuilder builder = new StringBuilder();

    /**
     * 获取授权信息
     * @return 返回授权信息列表
     */
    public LinkedHashMap<String, String> builderFilterChainDefinitionMap(){
        LinkedHashMap<String, String> permissionMap = new LinkedHashMap<>();

        // 固定的权限配置
        permissionMap.put("/css/**", "anon");
        permissionMap.put("/js/**", "anon");
        permissionMap.put("/fonts/**", "anon");
        permissionMap.put("/media/**", "anon");
        permissionMap.put("/pagecomponent/**", "anon");
        permissionMap.put("/login", "anon");
        permissionMap.put("/account/login", "anon");
        permissionMap.put("/account/checkCode/**", "anon");

        // 可变化的权限配置
        LinkedHashMap<String, String> permissions = getPermissionDataFromDB();
        if (permissions != null){
            permissionMap.putAll(permissions);
        }

        // 其余权限配置
        permissionMap.put("/", "authc");//        permissionMap.forEach((s, s2) -> {System.out.println(s + ":" + s2);});

        return permissionMap;
    }

    /**
     * 获取配置在数据库中的 URL 权限信息
     * @return 返回所有保存在数据库中的 URL 保存信息
     */
    private LinkedHashMap<String, String> getPermissionDataFromDB(){
        LinkedHashMap<String, String> permissionData = null;

        List<RolePermissionDO> rolePermissionDOS = rolePermissionMapper.selectAll();
        if (rolePermissionDOS != null){
            permissionData = new LinkedHashMap<>(rolePermissionDOS.size());
            String url;
            String role;
            String permission;
            for (RolePermissionDO rolePermissionDO : rolePermissionDOS){
                url = rolePermissionDO.getUrl();
                role = rolePermissionDO.getRole();

                // 判断该 url 是否已经存在
                if (permissionData.containsKey(url)){
                    builder.delete(0, builder.length());
                    builder.append(permissionData.get(url));
                    builder.insert(builder.length() - 1, ",");
                    builder.insert(builder.length() - 1, role);
                }else{
                    builder.delete(0, builder.length());
                    builder.append("authc,roles[").append(role).append("]");
                }
                permission = builder.toString();//                System.out.println(url + ":" + permission);
                permissionData.put(url, permission);
            }
        }

        return permissionData;
    }//    /**//     * 构造角色权限//     * @param role 角色//     * @return 返回 roles[role name] 格式的字符串//     *///    private String permissionStringBuilder(String role){//        builder.delete(0, builder.length());//        return builder.append("authc,roles[").append(role).append("]").toString();//    }}

  • 创建domain
/**
 * 用户账户信息(数据传输对象)
 * @author ken
 * @since 2017/2/26.
 */public class UserInfoDTO {

    /**
     * 用户ID
     */
    private Integer userID;

    /**
     * 用户名
     */
    private String userName;

    /**
     * 用户密码(已加密)
     */
    private String password;

    /**
     * 用户角色
     */
    private List<String> role = new ArrayList<>();

  • 创建realm
/**
 * 用户的认证与授权
 *
 * @author ken
 * @since 2017/2/26.
 */public class UserAuthorizingRealm extends AuthorizingRealm {

  /**
     * 对用户进行角色授权
     *
     * @param principalCollection 用户信息
     * @return 返回用户授权信息
     */
    @SuppressWarnings("unchecked")
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        // 创建存放用户角色的 Set
        Set<String> roles = new HashSet<>();

        //获取用户角色
        Object principal = principalCollection.getPrimaryPrincipal();
        if (principal instanceof String) {
            String userID = (String) principal;
            if (StringUtils.isNumeric(userID)) {
                try {
                    UserInfoDTO userInfo = userInfoService.getUserInfo(Integer.valueOf(userID));
                    if (userInfo != null) {
                        // 设置用户角色
                        roles.addAll(userInfo.getRole());
                    }
                } catch (UserInfoServiceException e) {
                    // do logger
                }
            }
        }

        return new SimpleAuthorizationInfo(roles);
    }

 /**
     * 对用户进行认证
     *
     * @param authenticationToken 用户凭证
     * @return 返回用户的认证信息
     * @throws AuthenticationException 用户认证异常信息
     * Realm的认证方法,自动将token传入,比较token与数据库的数据是否匹配
     * 验证逻辑是先根据用户名查询用户,
     * 如果查询到的话再将查询到的用户名和密码放到SimpleAuthenticationInfo对象中,
     * Shiro会自动根据用户输入的密码和查询到的密码进行匹配,如果匹配不上就会抛出异常,
     * 匹配上之后就会执行doGetAuthorizationInfo()进行相应的权限验证。
     */
    @SuppressWarnings("unchecked")
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws
            AuthenticationException {

        String realmName = getName();
        String credentials = "";

        // 获取用户名对应的用户账户信息
        try {
            UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;
            String principal = usernamePasswordToken.getUsername();

            if (!StringUtils.isNumeric(principal))
                throw new AuthenticationException();
            Integer userID = Integer.valueOf(principal);
            //依赖于/security.service.Interface.UserInfoService,UserInfoDTO中包含用户ID,用户名,密码,角色
            //wms_user表
            UserInfoDTO userInfoDTO = userInfoService.getUserInfo(userID);

            if (userInfoDTO != null) {
                Subject currentSubject = SecurityUtils.getSubject();
                Session session = currentSubject.getSession();

                // 设置部分用户信息到 Session
                session.setAttribute("userID", userID);
                session.setAttribute("userName", userInfoDTO.getUserName());
                //获取该用户的所属仓库
                List<RepositoryAdmin> repositoryAdmin = (List<RepositoryAdmin>) repositoryAdminManageService.selectByID(userInfoDTO.getUserID()).get("data");
                session.setAttribute("repositoryBelong", (repositoryAdmin.isEmpty()) ? "none" : repositoryAdmin.get(0).getRepositoryBelongID());


                // 结合验证码对密码进行处理
                String checkCode = (String) session.getAttribute("checkCode");
                String password = userInfoDTO.getPassword();
                if (checkCode != null && password != null) {
                    checkCode = checkCode.toUpperCase();
                    credentials = encryptingModel.MD5(password + checkCode);
                }
            }
            //比对账号密码
            //principal前端传来userid
            //credentials为数据库的密码,加chexkcode的MD5加密
            //realmName为com.ken.wms.security.realms.UserAuthorizingRealm_0
            return new SimpleAuthenticationInfo(principal, credentials, realmName);

        } catch (UserInfoServiceException | RepositoryAdminManageServiceException | NoSuchAlgorithmException e) {
            throw new AuthenticationException();
        }
    }

  • 创建controller
/**
 * 用户账户请求 Handler
 *
 * @author Ken
 * @since 017/2/26.
 */@Controller@RequestMapping("/account")public class AccountHandler {

// 获取当前的用户的 Subject,shiro
        Subject currentUser = SecurityUtils.getSubject();

  // 判断用户是否已经登陆
        if (currentUser != null && !currentUser.isAuthenticated()) {
            String id = (String) user.get(USER_ID);
            String password = (String) user.get(USER_PASSWORD);
            UsernamePasswordToken token = new UsernamePasswordToken(id, password);

            // 执行登陆操作
            try {
                //会调用realms/UserAuthorizingRealm中的doGetAuthenticationInfo方法
                currentUser.login(token);

                // 设置登陆状态并记录
                Session session = currentUser.getSession();
                session.setAttribute("isAuthenticate", "true");
                Integer userID_integer = (Integer) session.getAttribute("userID");
                String userName = (String) session.getAttribute("userName");
                String accessIP = session.getHost();
                systemLogService.insertAcce***ecord(userID_integer, userName, accessIP, SystemLogService.ACCESS_TYPE_LOGIN);

                result = Response.RESPONSE_RESULT_SUCCESS;
            } catch (UnknownAccountException e) {
                errorMsg = "unknownAccount";
            } catch (IncorrectCredentialsException e) {
                errorMsg = "incorrectCredentials";
            } catch (AuthenticationException e) {
                errorMsg = "authenticationError";
            } catch (SystemLogServiceException e) {
                errorMsg = "ServerError";
            }
        } else {
            errorMsg = "already login";
        }

认证流程

  1. 如果没有创建subject,创建一个subject
  2. 调用Subject.login(token)进行登录,其自动委托给Security Manager,调用之前必须通过SecurityUtils.setSecurityManager()设置
  3. SecurityManager负责真正的身份验证逻辑,它会委托给Authenticator进行身份验证
  4. Authenticator才是真正的身份验证者,shiro API中核心身份认证入口点,此处可以自定义插入自己的实现
  5. Authenticator 可能会委托给相应的 Authentication Strategy 进行多 Realm身份验证,默认 MudularRealmAuthenticator 会调用 AuthenticationStrategy进行多Realm身份验证
  6. Authenticator 会把相应的token传入Realm,从Realm获取身份验证信息,如果没有返回抛出异常表示身份验证失败了,此处可以配置多个Realm,按照相应顺序及策略进行访问。
上一篇:以'权限系统'开篇不晓得会不会遭到鄙视


下一篇:1/14笔记