JAVA学习之spring集成Shior

1、首先,我们需要将shior需要使用到的jar包依赖进项目中

<!-- shior版本号 -->
<shiro.version>1.2.1</shiro.version>
<!-- Shiro相关依赖开始 -->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>${shiro.version}</version>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-web</artifactId>
    <version>${shiro.version}</version>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-ehcache</artifactId>
    <version>${shiro.version}</version>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>${shiro.version}</version>
</dependency>
<!-- Shiro相关依赖结束 -->

2、依赖完成之后,我们需要增加配置文件了,首先需要将shior需要用到的可配置变量存放到一个文件中,统一使用,那么我们创建一个shior.properties,代码如下:

#shiro 加密方式
password.algorithmName=md5
#shiro 加密迭代次数
password.hashIterations=2
#shior 密码错误次数限制
password.retryCount=3

3、然后我们使用spring整合shior,创建一个配置文件shior-source.xml,代码如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans default-lazy-init="true"
       xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd" >

    <!-- 加载属性配置文件 -->
    <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="order" value="1"/>
        <property name="ignoreUnresolvablePlaceholders" value="true"/>
        <property name="locations">
            <list>
                <value>classpath:shior.properties</value>
            </list>
        </property>
    </bean>

    <!-- 配置SecurityManager对象(Shiro框架核心,负责调用相关组件实现用户身份认证,授权,缓存,会话管理等功能)-->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="Realm" ref="shiroUserRealm"/>
    </bean>
    <!-- 配置ShiroFilterFactoryBean对象(Shiro中会通过很多过滤器对WEB请求做预处理,这些过滤器的创建底层设计了一个工厂类) -->
    <bean id="shiroFilterFactory" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <!-- 注入SecurityManager对象 -->
        <property name="SecurityManager" ref="securityManager"/>
        <!-- 配置登录页面 -->
        <property name="LoginUrl" value="/login.htm"/>
        <!-- 定义过滤规则(哪些资源允许匿名访问,哪些资源必须授权访问)-->
        <property name="FilterChainDefinitionMap">
            <map>
                <!-- 说明:anon表示允许匿名访问, authc表示授权访问-->
                <entry key="/css/**" value="anon"/>
                <entry key="/db_sql/**" value="anon"/>
                <entry key="/images/**" value="anon"/>
                <entry key="/js/**" value="anon"/>
                <entry key="/META-INF/**" value="anon"/>
                <!-- 这里是授权的应用,以xml方式来管理,当然也有使用注解或jsp标签的方式,在授权时我们会讲解-->
                <!-- <entry key="/company/deleteCompany.do" value="perms[company:delete]" /> -->
                <!-- <entry key="/page/xtgl/userList.jsp" value="perms[xtgl:userList]"> -->
                <entry key="/doLogin.htm" value="anon"/>
                <entry key="/doLogout.do" value="logout"/>
                <entry key="/**" value="authc"/>


            </map>
        </property>
    </bean>
    <!-- 配置bean对象的生命周期管理 -->
    <bean id="lifecycleBeanPostProcessor"
          class="org.apache.shiro.spring.LifecycleBeanPostProcessor">
    </bean>
    <!-- 配置Bean对象的代理 -->
    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
          depends-on="lifecycleBeanPostProcessor">
    </bean>
    <!-- 配置授权Bean对象 -->
    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="SecurityManager" ref="securityManager"/>
    </bean>
    <!--自定义realm对象,这个我们需要自定义,其中有俩个方法,一个为登录验证,一个为权限验证-->
    <bean id="shiroUserRealm" class="com.privates.shior.realm.ShiroUserRealm">
        <!-- 密码验证方式 -->
        <property name="credentialsMatcher" ref="credentialsMatcher"/>
        <property name="cachingEnabled" value="false"/>
    </bean>

    <!-- 凭证匹配器,次方法定义了密码验证的一些规则,包括使用的是什么加密方式,加密迭代次数为几次
        RetryLimitHashedCredentialsMatcher这个类也需要我们自定义去实现,这个类的作用就是限制密码验证次数及将用户信息放入缓存-->
    <bean id="credentialsMatcher" class="com.privates.shior.RetryLimitHashedCredentialsMatcher">
        <constructor-arg ref="cacheManager" />
        <property name="hashAlgorithmName" value="${password.algorithmName}"/>
        <property name="hashIterations" value="${password.hashIterations}"/>
        <property name="storedCredentialsHexEncoded" value="true"/>
    </bean>

    <!-- 这是shior自带的缓冲,抄就可以了,没有需要改动的地方,当然也可以使用redis代替 -->
    <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager" >
        <property name="cacheManagerConfigFile" value="classpath:ehcache.xml"></property>
    </bean>
</beans>

4、根据配置文件中的注释,来一一增加我们需要自定义的一些东西,首先我们来增加ShiroUserRealm这个比较核心的类,需要继承AuthorizingRealm,代码如下:

package com.privates.shior.realm;

import com.privates.domain.User;
import com.privates.service.UserService;
import com.privates.utils.PasswordHelper;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
* 通过此对象获取用户身份相关信息,用户权限相关信息
* 间接的实现了Realm接口
* @author renyf24249
* @version $ID: ShiroUserRealm.java, v  2019/2/22 14:28 Exp $
* AuthenticatingRealm (提供了认证数据的获取方法)
*/
@Service
public class ShiroUserRealm extends AuthorizingRealm {

    @Autowired private UserService userService;

    /** 自定义缓存map(缓存用户权限信息) **/
    private Map<String,SimpleAuthorizationInfo> authorMap = new ConcurrentHashMap<String, SimpleAuthorizationInfo>();

    /**此方法提供认证数据的获取操作*/
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        UsernamePasswordToken upToken = (UsernamePasswordToken) token;
        String userName = upToken.getUsername();
        // 根据当前的请求登录用户名查询数据库中的用户信息,并判断是否存在该用户
        User user = userService.selectByUserName(userName);
        if (user == null) {
            throw new AuthenticationException("用户名不存在!");
        }
        //第一个参数为数据库中的用户名,第二个参数为数据库中的密码,第三个参数为,我们当初在insert用户信息时,密码加密时使用到的盐,作用为限制密码反推
        //第四个参数为获取我们自定义的这个类的名称   这里的意思就是将我们添加用户成功之后的用户名传给shior,shior根据当前记录的登录信息和我们的数据库中的用户登录信息作比对,验证登录
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user.getUserName(),
                user.getPassword(), ByteSource.Util.bytes(user.getCredentialsSalt()), super.getName());
        // TODO 加入redis缓存
        return info;
    }

    /** 此方法提供权限认证的操作,后文中会讲到 */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }
}

5、此时,我们的用户实体类中需要包含盐,每个人的盐是不一样的,所以我们需要记录下他的盐在数据库,用户类代码如下:

package com.privates.domain;

public class User {

    /** pk */
    private Integer id;

    /** 账号 */
    private String account;

    /** 密码 */
    private String password;

    /** 简称 */
    private String shortName;

    /** 真实姓名 */
    private String realName;

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

    /** 年龄 */
    private String age;

    /** 盐(加密时防反推) */
    private String salt;

    public Integer getId() { return id; }

    public void setId(Integer id) { this.id = id; }

    public String getAccount() { return account; }

    public void setAccount(String account) { this.account = account; }

    public String getPassword() { return password; }

    public void setPassword(String password) { this.password = password; }

    public String getShortName() { return shortName; }

    public void setShortName(String shortName) { this.shortName = shortName; }

    public String getRealName() { return realName; }

    public void setRealName(String realName) { this.realName = realName; }

    public String getUserName() { return userName; }

    public void setUserName(String userName) { this.userName = userName; }

    public String getAge() { return age; }

    public void setAge(String age) { this.age = age; }

    public String getSalt() { return salt; }

    public void setSalt(String salt) { this.salt = salt; }

    public String getCredentialsSalt() { return this.getSalt(); }
}

6、此时,我们再来写配置文件中缺少的密码适配器,即密码加密规则、次数限制、缓存等信息的管理类RetryLimitHashedCredentialsMatcher,需要继承HashedCredentialsMatcher,代码如下:

package com.privates.shior;

import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.ExcessiveAttemptsException;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheManager;
import org.springframework.beans.factory.annotation.Value;

import java.util.concurrent.atomic.AtomicInteger;

/**
* @author renyf24249
* @version $ID: RetryLimitHashedCredentialsMatcher.java, v  2019/2/22 15:09 Exp $
*/
public class RetryLimitHashedCredentialsMatcher extends HashedCredentialsMatcher {

    @Value("${password.retryCount}")
    private Integer retryCountSystem;

    private Cache<String, AtomicInteger> passwordRetryCache;

    /** 重试限制哈希凭证匹配器 */
    public RetryLimitHashedCredentialsMatcher(CacheManager cacheManager) {
        passwordRetryCache = cacheManager.getCache("passwordRetryCache");
    }

    /**
     * 功能描述: 做证书匹配
     * @author renyf24249
     * @param token
     * @param info
     * @return boolean
     */
    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
        String username = (String) token.getPrincipal();
        // 判断登录次数
        AtomicInteger retryCount = passwordRetryCache.get(username);
        if (retryCount == null) {
            retryCount = new AtomicInteger(0);
            passwordRetryCache.put(username, retryCount);
        }
        if (retryCount.incrementAndGet() > retryCountSystem) {
            throw new ExcessiveAttemptsException();
        }
        boolean matches = super.doCredentialsMatch(token, info);
        if (matches) {
            passwordRetryCache.remove(username);
        }
        return matches;
    }
}

注意:这里有个BUG,就是第一次登录错误次数为1,第二次登录错误次数为3,第三次登录次数为5,以此类推,这个需要在真实使用时去解决,我没有解决。

7、配置文件中缺少最后一步就是shior自带缓存的配置了,那么,我们创建一个ehcache.xml文件,代码如下:

<?xml version="1.0" encoding="UTF-8"?>
<ehcache name="shirocache">

    <diskStore path="java.io.tmpdir" />

    <!--
       name:缓存名称。
       maxElementsInMemory:缓存最大个数。
       eternal:对象是否永久有效,一但设置了,timeout将不起作用。
       timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
       timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
       overflowToDisk:当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中。
       diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
       maxElementsOnDisk:硬盘最大缓存个数。
       diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
       diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
       memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
       clearOnFlush:内存数量最大时是否清除。
    -->

    <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            overflowToDisk="true"
            maxElementsOnDisk="10000000"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU"
    />

    <!-- 登录记录缓存 锁定10分钟 -->
    <cache name="passwordRetryCache"
           eternal="false"
           maxElementsInMemory="0"
           timeToIdleSeconds="3600"
           timeToLiveSeconds="0"
           overflowToDisk="false"
           statistics="true">
    </cache>

    <cache name="authorizationCache" eternal="false" maxElementsInMemory="0"
           timeToIdleSeconds="3600" timeToLiveSeconds="0" overflowToDisk="false"
           statistics="true">
    </cache>

    <cache name="authenticationCache" eternal="false" maxElementsInMemory="0"
           timeToIdleSeconds="3600" timeToLiveSeconds="0" overflowToDisk="false"
           statistics="true">
    </cache>

    <cache name="shiro-activeSessionCache" eternal="false" maxElementsInMemory="0"
           timeToIdleSeconds="3600" timeToLiveSeconds="0" overflowToDisk="false"
           statistics="true">
    </cache>

</ehcache>

注意:如果文件位置放置的不一样,有可能会找不到文件位置,自己注意一下

8、此时,配置文件中的需要自定义的东西我们已经写完,然后需要做的就是,将shior的配置文件加载到web.xml中,并配置shior的filter,作用是拦截所有请求,判断是否授权认证成功,代码如下:

<!-- 指定Spring的配置文件,可以是多个,spring整合每个框架时都可以另写一个spring配置文件 -->
<!-- 否则Spring会默认从WEB-INF下寻找配置文件,contextConfigLocation属性是Spring内部固定的 -->
<!-- 通过ContextLoaderListener的父类ContextLoader的第120行发现CONFIG_LOCATION_PARAM固定为contextConfigLocation -->
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
        /WEB-INF/conf/spring/spring-context.xml,
        /WEB-INF/conf/shior/shior-source.xml
    </param-value>
</context-param>


<!-- 配置shiro过滤器(对请求进行拦截) -->
<filter>
    <filter-name>shiroFilter</filter-name>
    <!-- 此类型由谁提供(spring 框架):spring整合shiro的入口 -->
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    <init-param>
        <!-- 这个参数名在DelegatingFilterProxy中定义 -->
        <param-name>targetBeanName</param-name>
        <!-- 这个值在spring-shiro.xml配置文件中定义 -->
        <param-value>shiroFilterFactory</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>shiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

9、到此我们就把所有的shior需要使用的东西都写完了,接下来就是使用shior来做第一个功能了,登录认证,controller代码如下:

package com.skeyedu.controller;

import com.skeyedu.dao.model.Users;
import com.skeyedu.service.UsersService;
import com.skeyedu.util.CommonResult;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;

@Controller
public class LoginController {

    @Autowired
    private UsersService usersService;

    @RequestMapping("/doLogin.htm")
    public @ResponseBody CommonResult login(HttpServletRequest request, HttpServletResponse response, Users users) {
        CommonResult result = new CommonResult(); result.setIsSuccess(true);
        if (users == null || users.getUserAccount() == null) {
            result.setResultMsg("请输入用户名!"); return result;
        }
        try {
            //1.获取Subject对象
            Subject subject= SecurityUtils.getSubject();
            //2.通过Subject提交用户信息,交给shiro框架进行认证操作
            //2.1对用户进行封装(身份信息,凭证信息)
            UsernamePasswordToken token =
                    new UsernamePasswordToken(users.getUserAccount(), users.getUserPassword());
            //2.2对用户信息进行身份认证
            subject.login(token);
            result.setIsSuccess(true); result.setResultMsg("登陆成功!");
        } catch (UnknownAccountException | IncorrectCredentialsException e) {
            result.setResultMsg("用户名/密码错误");
        } catch (ExcessiveAttemptsException e) {
            // TODO: 此时应该设置用户的状态为锁定
            result.setResultMsg("登录失败多次,账户锁定10分钟");
        } catch (AuthenticationException e) {
            // 其他错误,比如锁定,如果想单独处理请单独catch处理
            result.setResultMsg("其他错误:" + e.getMessage());
        }
        return result;
    }

    @RequestMapping("/login.htm")
    public ModelAndView login() {
        ModelAndView view = new ModelAndView("login");
        return view;
    }

    @RequestMapping("/index")
    public ModelAndView index(HttpServletRequest request) {
        ModelAndView view = new ModelAndView("index");
        view.addObject("loginUser", request.getSession().getAttribute("loginUser"));
        return view;
    }

    @RequestMapping("/loginOut")
    public @ResponseBody CommonResult loginOut(HttpServletRequest request){
        request.getSession().removeAttribute("loginUser");
        return new CommonResult(true);
    }
}

注意:此时的登录功能只是做了最简单的功能,如有需要自己往上加吧

10、此时就可以尝试去登陆了,当然了,在添加用户的时候记得要将密码加密之后再存入数据库,不然验证是通不过的哟,还要记得保存“盐”到数据库,加密代码如下:

package com.skeyedu.shior;

import com.skeyedu.dao.model.Users;
import org.apache.shiro.crypto.RandomNumberGenerator;
import org.apache.shiro.crypto.SecureRandomNumberGenerator;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

/**
* @author renyf24249
* @version $ID:PasswordHelper.java, v  2019/2/22 14:56 Exp $
*/
@Service
public class PasswordHelper {

    private RandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator();

    @Value("${password.algorithmName}")
    private String algorithmName;

    @Value("${password.hashIterations}")
    private int hashIterations;

    public void encryptPassword(Users user) {
        // 判断是否需要生成盐
        if (user.getSalt() == null) {
            //生成盐(部分,需要存入数据库中)
            String random = randomNumberGenerator.nextBytes().toHex();
            user.setSalt(random);
        }
        String newPassword = new SimpleHash(algorithmName, user.getUserPassword(),
                ByteSource.Util.bytes(user.getCredentialsSalt()), hashIterations).toHex();
        user.setUserPassword(newPassword);
    }
}

11、然后再添加用户的时候调用一下PasswordHelper的encryptPassword方法就可以了,试一下吧;

注意:其中的密码加密迭代次数需要所有地方都同意,加密方式也是一样,如果不一样就会出现莫名其妙的问题

12、再次之前遇到的问题,第一个就是密码加密方式不正确,导致和shior加密之后的密码进行校验时不匹配,搞了好久,解决办法:一直跟着源码看进去,看到HashedCredentialsMatcher类中写了加密的方法hashProvidedCredentials,源代码如下:

protected Hash hashProvidedCredentials(Object credentials, Object salt, int hashIterations) {
    String hashAlgorithmName = this.assertHashAlgorithmName();
    return new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations);
}

这说明,我们页面上传入12345这个密码时,shior是利用上述方法加密的,那么我们也可以在insert用户时,使用此方式将密码放入数据库,当然了,为了保密性强,还可以再次使用md5加密,匹配校验时先解密再传给shior进行匹配,故此问题解决。

第二个问题就是页面报错,406,一直是在搞json的jar包,springmvc配置json转换,然后增加注解什么的,其实根本的问题在于@RequestMapper中的url的地址后缀问题,我写的是htm,这个后缀springmvc不支持,所以才会解析不了。

13、关于授权问题使用shior,首先需要有权限表,而我们一般系统都是权限对应角色,角色再对应用户,那么我们要做授权,就要拥有这些数据,这里就不一一贴代码了,加入我们拥有一组权限,如下:

用户名:root 角色:人事,前台 权限:用户管理 考勤管理 ,这就是我们的权限信息及角色信息,那么我们需要怎么做呢,首先我们要去实现shior的授权操作,此操作在上文中我们定义的ShiorUserRealm中实现,代码如下:

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    // 获取当前登录用户
    String userName=(String) SecurityUtils.getSubject().getPrincipal();
    SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
    // 定义一个角色集合
    Set<String> roles=new HashSet<String>();
    // 查询当前用户所拥有的角色列表
    List<Map<String, Object>> rolesByUserName = roleDao.getRolesByUserName(userName);
    for(Map<String, Object> role : rolesByUserName) {
        // 将这些角色放入先前定义的集合中,放入的值是什么,后期写代码时,使用的权限设置的注解中就需要写什么值
        // 所以这里建议使用枚举类去处理,方便,又避免写错
        roles.add(role.get("roleName").toString());
    }
    // 查询当前用户所拥有的权限列表
    List<Map<String, Object>> permissionsByUserName = permissionDao.getPermissionsByUserName(userName);
    for(Map<String, Object> permission:permissionsByUserName) {
        // 将权限信息给到shior保存起来,同上所述,这里给shior的字符串就是后期写代码时要使用到的控制权限注解中的参数
        // 当某个用户访问某个带有权限注解的controller时,shior就会来这里取该用户的权限,判断是否有将要访问的请求的权限
        info.addStringPermission(permission.get("name").toString());
    }
    // 将角色集合给shior
    info.setRoles(roles);
    return info;
}

14、此时我们将用户管理的这个controller入口给他加上权限注解,代码如下:

/** 此注解表示,必须拥有用户管理的这个权限才能访问 */
@RequiresPermissions({"用户管理"})
@RequestMapping("/index")
public ModelAndView index() {
    return new ModelAndView("user/userList");
}

/** 此注解表示,必须拥有人事这个角色才能访问 */
@RequiresRoles({"人事"})
@RequestMapping(value = "/loadData")
public @ResponseBody DataTables loadData(Users users) {
    return new DataTables(usersService.queryByMap(users));
}

此时我们去尝试请求,发现可以请求,但是我们在shior的授权的方法中打了断点,发现并没有进去方法,这说明,我们加的注解没有起作用,那么网上查阅资料,说有俩种办法,第一种,由于我们的shior是通过spring整合的,而spring并没有去管理controller,所以我们在controller中加入权限注解,spring并没有扫描到,自然没起作用,将注解转移到service层就可以了,这个当然不是我们想要的答案,毕竟service中的业务有很多地方在使用,加一个等于控制了多个业务,耦合性太高,第二种是在springmvc的配置文件中加入如下代码:

<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>

而且这段代码还必须加载springmvc的第一个配置文件中,不然也不管用,我们加入进去后,发现,断点进去了,注解起作用了。

15、shior的五种权限注解说明

@RequiresAuthentication : 验证用户是否登录,等同于方法subject.isAuthenticated() 结果为true时。

@RequiresUser : 验证用户是否被记忆,user有两种含义:

    一种是成功登录的(subject.isAuthenticated() 结果为true);
    另外一种是被记忆的(subject.isRemembered()结果为true)。

@RequiresGuest : 验证是否是一个guest的请求,与@RequiresUser完全相反。

     换言之,RequiresUser  == !RequiresGuest。 此时subject.getPrincipal() 结果为null.

@RequiresRoles 例如:@RequiresRoles(“aRoleName”);

    如果subject中有aRoleName角色才可以访问方法someMethod。如果没有这个权限则会抛出异常AuthorizationException。

@RequiresPermissions 例如: @RequiresPermissions({“file:read”, “write:aFile.txt”} )

    要求subject中必须同时含有file:read和write:aFile.txt的权限才能执行方法someMethod()。否则抛出异常AuthorizationException

16、授权实现之后我们发现一个问题,那就是当我们没有某种权限时,我们的系统都是在后台报错,这不符合业务需要啊,我们需要的应该是不显示此菜单,或者是再次点就是提示用户没有此权限,那么我们应该怎么做呢?

查询网上的方法:

需要在springmvc中增加如下配置:

<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
        <property name="exceptionMappings">
            <props>
                <!-- 错误异常转发jsp页面 -->
                <prop key="org.apache.shiro.authz.UnauthorizedException">/unauthorized.jsp</prop>
                <prop key="org.apache.shiro.authz.UnauthenticatedException">/unauthenticated.jsp</prop>
            </props>
        </property>
    </bean>

或者自定义异常处理类: SimpleMappingExceptionResolver,上述配置中改掉

然后自定义一个异常,继承SimpleMappingExceptionResolver,重写如下方法:

@Override
    protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response,
                                              Object handler, Exception ex) {
        try {
            // Expose ModelAndView for chosen error view.
            BaseResult result = new BaseResult();
            if (ex instanceof UnauthorizedException) {
                result.setMsg(RespMSG.MSG_UNAUTHORIZED );
                result.setStatus(RespMSG.STATUS_UNAUTHORIZED);
            } else if (ex instanceof UnauthenticatedException) {
                result.setMsg(RespMSG.MSG_UNAUTHENTICATED );
                result.setStatus(RespMSG.STATUS_UNAUTHENTICATED);
            } else {
                result.setMsg(RespMSG.MSG_FAILLED );
                result.setStatus(RespMSG.STATUS_FAILLED);
            }
            response.setHeader("Content-type", "text/html;charset=UTF-8");
            PrintWriter writer = response.getWriter();
            writer.write(new Gson().toJson(result));
            writer.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

看着比较麻烦,没有亲自尝试,有时间再试;
shior分享就到这里;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值