Shiro的用法以及配置

2019-02-19  本文已影响0人  kiring

shiro认证的流程(非常重要)

shiro认证流程.png
认证流程.png

为什么要重写realm?如何重写realm?

在执行调用subject.login(token)方法后,会把subject以及token都传进去
(subject是从环境中取出的,也就是说subject是可以代表当前用户所处的上下文环境,也即是说可以拿到当前环境的realm的真实数据);
程序会先判断账号,再判断密码。

Realm的继承体系

Realm的继承体系.png
使用AuthorizingRealm来继承
shrio进行认证的底层的逻辑主要在realm.doGetAuthenticationInfo(token)中,原生的方法里
(在一开始创建securityManager实例对象的时候,会将用户指定的realm(ini方式)加载进环境)
有个getUser->realm.getUser(upToken.getUsername())(realm在初始化securityManager的时候就加载进内存,所以这里的数据源是从环境中来的)方法来获得account(这就是一个AuthenticationInfo),如果此时account为空,那么就不再判断密码而是直接报错出来,
如果这个account有值,那么程序会拿着这个account继续往下判断密码
而我们的逻辑主要在于一开始的数据从哪来,怎么进行第一步的判断账号
所以 我们重写realm主要就是重写doGetAuthenticationInfo(token)方法,在该方法中使用service来从数据库获得数据,并进行初次判断。

因为原生的realm里doGetAuthenticationInfo(token)方法中只进行用户账号的判断,然后将account(info)返回,交给后续程序处理(realm.assertCredentialsMatch(token, info)),这个info是包含数据源中的信息的,相当于一个标准,用来被比较。)
所以在我们重写的只需要把一个包含我们doGetAuthenticationInfo(token)方法中,只需要把标准的密码封装到一个info对象即可,这里自己
new SimpleAuthenticationInfo(employee,employee.getPassword(),getName());将其返回就好,剩下的交给shrio。

@Autowired
private IEmployeeService employeeService;

/*通过注入的方式给realm设置凭证匹配器*/
@Autowired
@Override
public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
    super.setCredentialsMatcher(credentialsMatcher);
}

//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
    Employee employee = employeeService.getByUsername((String)token.getPrincipal());
    if(employee!=null){
        return new SimpleAuthenticationInfo(employee,employee.getPassword(), ByteSource.Util.bytes(employee.getName()),getName());
    }
    return null;
}
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    <property name="realm" ref="myWebRealm"/>
</bean>

怎么使用Spring和shrio进行认证

在JavaSE中,我们只是在做简单的验证模拟,这时我们已经是在做登录操作了!
那么在JavaEE中,我们要怎么判断用户的哪些行为是登录操作?

那么就像我们以前写原生的servlet一样,我们要把filter(那么现在我们需要用到这个shiroFilter已经写好,我们直接用)交给tomcat来管理,此时在web.xml来配置filter(DelegatingFilterProxy),那么现在我们需要用到这个shiroFilter,显然,有了Spring,我们不可能自己创建,而且这个filter也就是单例就好

配置Spring配置文件

<!--注意:名字必须要和web.xml中配置的名字一致-->
    <!-- 定义ShiroFilter -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    <property name="securityManager" ref="securityManager"/>
    <property name="loginUrl" value="/login.html"/>
    <property name="filterChainDefinitions">
        <value>
            /js/**=anon
            /bootstrap-3.3.7-dist/**=anon
            /jQuery/**=anon
            /images/**=anon
            /css/**=anon
            /style/**=anon
            /logout.do=logout
            /**=authc
        </value>
     </property>
    <property name="filters">
        <map>
            <entry key="authc" value-ref="myCRMFormFilter"/>
        </map>
    </property>
</bean>

<!-- 配置安全管理器SecurityManager 在web环境下使用默认web安全管理器-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    <property name="realm" ref="myWebRealm"/>
</bean>

登录表单提交的用户名和密码名字必须是username和passord,通过源码发现其底层就是req.getParameter("username")

授权操作

授权操作的整体实现和认证差不多,需要使用到我们自己的业务和数据源,在web环境下开发就需要使用我们自己定义的数据源,同样还是继承AuthorizingRealm,重写其中的doGetAuthorizationInfo方法
在这里我们不需要判断权限,只需要将用户的权限和角色查出,丢进info里(new SimpleAuthorizationInfo())即可

//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    //创建一个空的AuthorizationInfo
    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
    //拿到当前用户
    Employee employee = (Employee)principalCollection.getPrimaryPrincipal();
    //判断是否是超级管理员
    if(employee.getAdmin()){
        //给管理员设置用户信息,并且可以查询所有权限
        info.addRole("admin");
        info.addStringPermission("*:*");
        return info;
    }else{
        //从数据库中查到所对应的角色和权限
        List<String> roles = employeeService.getRolesByEmployeeId(employee.getId());
        Set<String> permissions = employeeService.getPermissionsByEmployeeId(employee.getId());
        info.addRoles(roles);
        info.addStringPermissions(permissions);
        return info;
    }
}

怎么去判断权限

就像我们之前做RBAC的一样,既然要有权限控制,那么资源所对应的权限是我们规定的。

  1. 使用Shiro时,使用容器对象的getBeansWithAnnotation()获得的controller是可以包括贴有@Controller注解的类的子类的 (这里的意思是说,Shrio会自动将贴有标签的Controller类动态生成相应的代理类,而@Controller这个注解是没有继承的,但是SpringMVC还是能查找到,并且如果有子类只会找子类而不会找其父类) 然后我们要判断这些字节码对象是否是属于cglib的代理类 (AopUtils.isCglibProxy(controller))

  2. 再用这些判断后的字节码对象获得其父类字节码(controller.getClass().getSuperclass())(@RequiresPermissions标签不继承)找到贴有这些注解的方法,获取其方法体上的注解字节码然后拿到内容,存进数据库中

总的来说

Shiro是先进行认证,认证通过后进行授权,什么时候校验权限?访问方法的时候,代理类增强的方法会去检验

一些配置

<!--配置凭证匹配器,并将其设置给realm(通过注入的方式)-->
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
    <property name="hashAlgorithmName" value="md5"/>
</bean>
<!--配置权限AoP织入增强功能-->
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
    <property name="securityManager" ref="securityManager"/>
</bean>

缓存管理器

使用shiro自带的缓存管理器,这时权限和角色信息(shrio只负责这两块,所以也只缓存这两块,不需另外指明)缓存到内存中,这样就不会刷新页面访问同样的资源时还要执行数据源中的授权方法,这样就不用再发SQL了

<!-- 缓存管理器开始 -->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
    <property name="cacheManager" ref="ehCacheManager"/>
</bean>
<bean id="ehCacheManager" class ="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
    <property name="configLocation" value="classpath:shiro-ehcache.xml" />
    <property name="shared" value="true"/>
</bean>

shiro和freemark的兼容配置(在FreeMark中使用Shiro标签)

要增强FreeMark的功能,而又要按照FreeMark的规范,这时我们可以继承FreeMark的类再重写自己的方法,这里我们需要继承FreeMark的配置类,拓展Shiro的便签类(记得加依赖)

public class MyCRMFreeMarkerConfig extends FreeMarkerConfigurer {
@Override
public void afterPropertiesSet() throws IOException, TemplateException {
    super.afterPropertiesSet();
    Configuration cfg = this.getConfiguration();
    cfg.setSharedVariable("shiro", new ShiroTags());//shiro标签
}
}

此时配置文件中就要引入我们自己的FreeMark的配置

<!--配置freeMarker的模板路径 -->
<bean class="cn.kiring.crm.shiro.MyCRMFreeMarkerConfig">
    <!-- 配置freemarker的文件编码 -->
    <property name="defaultEncoding" value="UTF-8" />
    <!-- 配置freemarker寻找模板的路径(相当于前缀) -->
    <property name="templateLoaderPath" value="/WEB-INF/views/" />
</bean>

<!--freemarker视图解析器 -->
<bean class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
    <!-- 是否在model自动把session中的attribute导入进去; -->
    <property name="exposeSessionAttributes" value="true" />
    <!-- 配置逻辑视图自动添加的后缀名 -->
    <property name="suffix" value=".ftl" />
    <!-- 配置视图的输出HTML的contentType -->
    <property name="contentType" value="text/html;charset=UTF-8" />
</bean>

关于登录

通过查看认证过滤器中isLoginSubmission()方法就能发现,这个方法内部有进行判断这个请求是什么方式,如果是POST方式,就会进行登录验证操作,否则就是普通的访问这个资源,又因为我们在shiro.xml中的过滤器的配置上配置了loginUrl属性值,所以过滤器不会拦截这个请求。

通过查看认证过滤器FormAuthenticationFilter里的getUsername()和getPassword()可以发现shiro底层就是使用request.getParameter("username")和request.getParameter("password")来获取用户登录数据并装进token的

上一篇下一篇

猜你喜欢

热点阅读