SpringBoot-安全框架Shiro使用及总结

Apache Shiro是Java的一个安全框架。相比于Spring Security,功能没那么强大,但是简单许多,下面我们了解的是在SpringBoot中集成Shiro框架。

一.Shiro框架介绍:
本次我pom.xml用的Shiro版本是

org.apache.shiro
shiro-spring
1.4.0

1.认证与授权相关基本概念

两个基本的概念

安全实体:系统需要保护的具体对象数据

权限:系统相关的功能操作,例如基本的CRUD

Authentication(认证):身份认证/登录,验证用户是不是拥有相应的身份-即登录;

Authorization(授权):授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;

Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的;

Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;

Web Support:Web支持,可以非常容易的集成到Web环境;

Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;

Concurrency:shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;

Testing:提供测试支持;

Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;

Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。

二.核心组件核心类
1.Subject:当前用户,SUbject可以是一个人,也可以是第三方服务,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等;即一个抽象概念;所有Subject都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager;可以把Subject认为是一个门面;SecurityManager才是实际的执行者;
2.SecurityManager:安全管理器,管理所有Subject,可以配合内部安全组件,你可以把它看成DispatcherServlet前端控制器。

3.principals:身份,即主体的标识属性,可以是任何东西,如用户名、邮箱等,唯一即可。一个主体可以有多个principals,但只有一个Primary principals,一般是用户名/密码/手机号。
4.credentials:证明/凭证,即只有主体知道的安全值,如密码/数字证书等。
最常见的principals和credentials组合就是用户名/密码了。

5.Realms:用于进行权限信息的验证,需要自己实现。
6.Realm 本质上是一个特定的安全 DAO:它封装与数据源连接的细节,得到Shiro 所需的相关的数据。
域,Shiro从从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。
在配置 Shiro 的时候,你必须指定至少一个Realm 来实现认证(authentication)和/或授权(authorization)。
我们需要实现Realms的Authentication 和 Authorization。其中 Authentication 是用来验证用户身份,Authorization 是授权访问控制,用于对用户进行的操作授权,证明该用户是否允许进行当前操作,如访问某个链接,某个资源文件等。

7.SimpleHash,可以通过特定算法(比如md5)配合盐值salt,对密码进行多次加密。

三、Shiro配置

1.SpringBoot集成Shiro一般通过java代码配合@Configuration和@Bean配置。
2.Shiro的核心是通过Filter实现的,Shiro中的Filter是通过URL规则来进行过滤和权限校验,因此我们需要定义一些关于URL的规则喝访问权限。
3.SpringBoot集成Shiro,我们需要写来给你个类,ShiroConfiguration-用来配置Shiro,注入各种Bean(包括过滤器(shiroFilter)、安全事务管理器(SecurityManager)、密码凭证(CredentialsMatcher)、aop注解支持(authorizationAttributeSourceAdvisor)等等) 和继承了AuthorizingRealm的Realm类,包括登陆认证(doGetAuthenticationInfo)、授权认证(doGetAuthorizationInfo)。

四.代码实现
ShiroConfiguration类:

package com.example.demo.config;

import cn.licoy.wdog.core.config.mybatis.UserRealm;
import cn.licoy.wdog.core.config.shiro.CredentialsMatcher;
import org.apache.log4j.Logger;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.Cookie;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;

import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;

@Configuration
public class ShiroConfiguration {
private Logger logger=Logger.getLogger(ShiroConfiguration.class);

@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){
    ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();
    //设置安全管理器
    shiroFilterFactoryBean.setSecurityManager(securityManager);
    //默认跳转到登陆页面
    shiroFilterFactoryBean.setLoginUrl("/login");
    //登陆成功后的页面
    shiroFilterFactoryBean.setSuccessUrl("/index");
    shiroFilterFactoryBean.setUnauthorizedUrl("/403");

    //自定义过滤器
    Map filterMap=new LinkedHashMap<>();
    shiroFilterFactoryBean.setFilters(filterMap);
    //权限控制map
    Map filterChainDefinitionMap=new LinkedHashMap<>();
    // 配置不会被拦截的链接 顺序判断
    filterChainDefinitionMap.put("/static/**", "anon");
    //配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
    filterChainDefinitionMap.put("/logout", "logout");

// //:这是一个坑呢,一不小心代码就不好使了;
// //
// filterChainDefinitionMap.put("/**", "anon");

    shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
    return shiroFilterFactoryBean;
}

/**
 * 核心的安全事务管理器
 * 设置realm、cacheManager等
 * @return
 */
@Bean
public SecurityManager securityManager(){
    DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager ();
    //设置realm
    securityManager.setRealm( myShiroRealm(  )  );
    securityManager.setRememberMeManager(rememberMeManager());
    securityManager.setCacheManager( ehCacheManager() );
    return securityManager;
}


/**
 * 身份认证Realm,此处的注入不可以缺少。否则会在UserRealm中注入对象会报空指针.
 * @return
 */
@Bean
public UserRealm myShiroRealm(  ){
    UserRealm myShiroRealm = new UserRealm();
    myShiroRealm.setCredentialsMatcher(  hashedCredentialsMatcher() );
    return myShiroRealm;
}

/**
 * 配置自定义的密码比较器
 * @return
 */
@Bean
public CredentialsMatcher credentialsMatcher(){
    return  new CredentialsMatcher();
}


/**
 * 哈希密码比较器。在myShiroRealm中作用参数使用
 * 登陆时会比较用户输入的密码,跟数据库密码配合盐值salt解密后是否一致。
 * @return
 */
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(){
    HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
    hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用md5算法;
    hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5( md5(""));
    return hashedCredentialsMatcher;
}

/**
 *  shiro缓存管理器;
 * 需要注入对应的其它的实体类中: 安全管理器:securityManager
 * 可见securityManager是整个shiro的核心;
 * @return
 */
@Bean
public EhCacheManager ehCacheManager(){
    logger.info("------------->ShiroConfiguration.getEhCacheManager()执行");
    EhCacheManager cacheManager=new EhCacheManager();
    cacheManager.setCacheManagerConfigFile("classpath:ehcache-shiro.xml");
    return cacheManager;
}

/**
 * 记住我管理器
 * @return
 */
@Bean
public CookieRememberMeManager rememberMeManager() {
    CookieRememberMeManager cookieRememberMeManager=new CookieRememberMeManager();
    cookieRememberMeManager.setCookie(rememberMeCookie());
    //rememberMe cookie加密的密钥  默认AES算法

// cookieRememberMeManager.setCipherKey();
return cookieRememberMeManager;
}

/**
 * cookie对象
 * @return
 */
@Bean
public Cookie rememberMeCookie() {
    SimpleCookie simpleCookie=new SimpleCookie("rememberMe");
    //记住我cookie生效时间,单位秒
    simpleCookie.setMaxAge(3600);
    return simpleCookie;
}


/**
 *  开启shiro aop注解支持.
 *  使用代理方式;所以需要开启代码支持;否则@RequiresRoles等注解无法生效
 * @param securityManager
 * @return
 */
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
    AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
    authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
    return authorizationAttributeSourceAdvisor;
}

/**
 * Shiro生命周期处理器
 * @return
 */
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
    return new LifecycleBeanPostProcessor();
}

/**
 * 自动创建代理
 * @return
 */
@Bean
@DependsOn({"lifecycleBeanPostProcessor"})
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
    DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
    advisorAutoProxyCreator.setProxyTargetClass(true);
    return advisorAutoProxyCreator;
}

}


自定义UserRealm-(MyRealm)类:

package cn.licoy.wdog.core.config.mybatis;
import com.example.demo.pojo.SysUserRole;
import com.example.demo.pojo.User;
import com.example.demo.service.UserSerevice;
import com.example.demo.utils.State;
import org.apache.log4j.Logger;
import org.apache.shiro.authc.*;
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 javax.annotation.Resource;
import java.util.HashSet;
import java.util.Set;

public class UserRealm extends AuthorizingRealm {
@Resource(name = "userServiceImpl")
private UserSerevice userService;

private Logger logger=Logger.getLogger(UserRealm.class);

/**
 * 提供用户信息,返回权限信息
 * @param principals
 * @return
 */
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    logger.info("---------------------------->授权认证:");
    SimpleAuthorizationInfo authorizationInfo=new SimpleAuthorizationInfo();
    String userName=(String) principals.getPrimaryPrincipal();
    String userId=userService.findUserIdByName(userName);
    Set roleIdSet=userService.findRoleIdByUid( Integer.parseInt(userId) );
    Set roleSet=new HashSet<>();
    Set  pemissionIdSet=new HashSet<>();
    Set  pemissionSet=new HashSet<>();
    for(SysUserRole roleInfo : roleIdSet) {
        int roleId=roleInfo.getRoleId();
        roleSet.add( userService.findRoleByRoleId( roleId  ) );
        //将拥有角色的所有权限放进Set里面,也就是求Set集合的并集
        pemissionIdSet.addAll( userService.findPermissionIdByRoleId(  roleId ));
    }
    for(int permissionId : pemissionIdSet) {
        String permission= userService.findPermissionById( permissionId ).getPermission() ;
        pemissionSet.add(  permission );
    }
    // 将角色名称提供给授权info
    authorizationInfo.setRoles( roleSet );
    // 将权限名称提供给info
    authorizationInfo.setStringPermissions(pemissionSet);

    return authorizationInfo;
}

/**
 * 提供帐户信息,返回认证信息
 * @param authenticationToken
 * @return
 * @throws AuthenticationException
 */
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    logger.info("---------------------------->登陆验证:");
    String userName=(String)authenticationToken.getPrincipal();
    User user=userService.findUserByName(userName);
    if(user==null) {
        //用户不存在就抛出异常
        throw new UnknownAccountException();
    }

//State.LOCKED="2" 用户被锁定状态,自定义
if( State.LOCKED.equals( user.getState() ) ) {
//用户被锁定就抛异常
throw new LockedAccountException();
}
//密码可以通过SimpleHash加密,然后保存进数据库。
//此处是获取数据库内的账号、密码、盐值,保存到登陆信息info中
SimpleAuthenticationInfo authenticationInfo=new SimpleAuthenticationInfo(user.getUserName(),
user.getPassword(),
ByteSource.Util.bytes(user.getSalt()) ,
getName()); //realm name

    return authenticationInfo;
}

}

更具体的代码,参见码云:
https://gitee.com/lufff1458/Shiro-Demo

可以参考watchdog整个框架中的shiro部分:
https://gitee.com/licoy/watchdog-framework/tree/master/src/main/java/cn/licoy/wdog/core/config

参考博客:

http://www.ityouknow.com/springboot/2017/06/26/springboot-shiro.html

你可能感兴趣的:(SpringBoot-安全框架Shiro使用及总结)