Springboot2.0整合Shiro框架系列-简单登录认证(一)

  1. Shiro简介
    Apache Shiro 是 Java 的一个安全框架。Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在 JavaSE 环境,也可以用在JavaEE 环境。Shiro 可以帮助我们完成,认证、授权、加密、会话管理、与Web集成、缓存等。
    Shiro的主要框架图:
    在这里插入图片描述

    Shiro主要组成简单说明:

    Subject
    Subject即主体,外部应用与subject进行交互,subject记录了当前操作用户,将用户的概念理解为当前操作的主体,可能是一个通过浏览器请求的用户,也可能是一个运行的程序。 Subject在shiro中是一个接口,接口中定义了很多认证授相关的方法,外部程序通过subject进行认证授,而subject是通过SecurityManager安全管理器进行认证授权
    SecurityManager
    SecurityManager即安全管理器,对全部的subject进行安全管理,它是shiro的核心,负责对所有的subject进行安全管理。通过SecurityManager可以完成subject的认证、授权等,实质上SecurityManager是通过Authenticator进行认证,通过Authorizer进行授权,通过SessionManager进行会话管理等。
    SecurityManager是一个接口,继承了Authenticator, Authorizer, SessionManager这三个接口。
    Authenticator
    Authenticator即认证器,对用户身份进行认证,Authenticator是一个接口,shiro提供ModularRealmAuthenticator实现类,通过ModularRealmAuthenticator基本上可以满足大多数需求,也可以自定义认证器。
    Authorizer
    Authorizer即授权器,用户通过认证器认证通过,在访问功能时需要通过授权器判断用户是否有此功能的操作权限。
    realm
    Realm即领域,相当于datasource数据源,securityManager进行安全认证需要通过Realm获取用户权限数据,比如:如果用户身份数据在数据库那么realm就需要从数据库获取用户身份信息。
    SessionManager
    sessionManager即会话管理,shiro框架定义了一套会话管理,它不依赖web容器的session,所以shiro可以使用在非web应用上,也可以将分布式应用的会话集中在一点管理,此特性可使它实现单点登录。
    SessionDAO
    SessionDAO即会话dao,是对session会话操作的一套接口,比如要将session存储到数据库,可以通过jdbc将会话存储到数据库。
    CacheManager
    CacheManager即缓存管理,将用户权限数据存储在缓存,这样可以提高性能。
    Cryptography
    Cryptography即密码管理,shiro提供了一套加密/解密的组件,方便开发。比如提供常用的散列、加/解密等功能。

  2. Springboot2.0整合Shiro实践

    该实践项目选用Springboot2.0 、Jpa 、Ehcache 、Swagger2、Fastjson、Shiro。
    项目地址:https://github.com/wlcloudy/springboot-shiro

    Shiro配置

    配置拦截器,定义拦截URL权限

	    @Bean
	    public ShiroFilterFactoryBean shirFilter(DefaultWebSecurityManager securityManager) {
	
	        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
	        shiroFilterFactoryBean.setSecurityManager(securityManager);
	        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
	        // 静态资源匿名访问
	        filterChainDefinitionMap.put("/static/**", "anon");
	        // Swagger
	        filterChainDefinitionMap.put("/swagger-ui.html", "anon");
	        filterChainDefinitionMap.put("/webjars/**", "anon");
	        filterChainDefinitionMap.put("/swagger-resources/**", "anon");
	        filterChainDefinitionMap.put("/v2/api-docs", "anon");
	        // 登录匿名访问
	        filterChainDefinitionMap.put("/api/v1/login", "anon");
			filterChainDefinitionMap.put("/api/v1/logout", "logout");	
	        // 其他路径均需要身份认证,一般位于最下面,优先级最低
	        filterChainDefinitionMap.put("/api/v1/**", "authc");
	        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
	        // 登录的路径
	//		shiroFilterFactoryBean.setLoginUrl("/login");		
			// 登录成功后跳转的路径
	//		shiroFilterFactoryBean.setSuccessUrl("/index");	
			// 验证失败后跳转的路径
	//		shiroFilterFactoryBean.setUnauthorizedUrl("/403");	
	        Map<String, Filter> filters = new LinkedHashMap<String, Filter>();
	        JsonFilter jsonFilter = new JsonFilter();
	        filters.put("login", jsonFilter);
	        shiroFilterFactoryBean.setFilters(filters);
	        return shiroFilterFactoryBean;
	    }

密码匹配器,主要是定义密码匹配规则,可以自定义。如下图默认将密码明文MD5两次进行匹配。

	// 默认实现
	@Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new MyHashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("MD5");
        hashedCredentialsMatcher.setHashIterations(2);
        return hashedCredentialsMatcher;
    }
    
    // 自定义实现
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        MyHashedCredentialsMatcher myHashedCredentialsMatcher = new MyHashedCredentialsMatcher();
        myHashedCredentialsMatcher.setHashAlgorithmName("MD5");
        myHashedCredentialsMatcher.setHashIterations(2);
        return myHashedCredentialsMatcher;
    }
    
	@Component
	public class MyHashedCredentialsMatcher extends HashedCredentialsMatcher {
	    @Override
	    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
	        //如果是微信登录方式
	        if (WeChatToken.class.isAssignableFrom(token.getClass())) {
	            return true;
	        }
	        return super.doCredentialsMatch(token, info);
	    }
	}

缓存管理器,本例集成的是Ehcache缓存实现

 	@Bean
    public EhCacheManager ehCacheManager(CacheManager cacheManager) {
        EhCacheManager em = new EhCacheManager();
        //将ehcacheManager转换成shiro包装后的ehcacheManager对象
        em.setCacheManager(cacheManager);
        return em;
    }

	<?xml version="1.0" encoding="UTF-8"?>
	<ehcache name="shiroCache" updateCheck="false">
	    <!--
	       name:缓存名称。
	       maxElementsInMemory:缓存最大数目
	       maxElementsOnDisk:硬盘最大缓存个数。
	       eternal:对象是否永久有效,一但设置了,timeout将不起作用。
	       overflowToDisk:是否保存到磁盘,当系统当机时
	       timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
	       timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
	       diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
	       diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
	       diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
	       memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
	        clearOnFlush:内存数量最大时是否清除。
	         memoryStoreEvictionPolicy:
	            Ehcache的三种清空策略;
	            FIFO,first in first out,这个是大家最熟的,先进先出。
	            LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。
	            LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
	    -->
	
	    <defaultCache
	            eternal="false"
	            maxElementsInMemory="1000"
	            overflowToDisk="false"
	            diskPersistent="false"
	            timeToIdleSeconds="0"
	            timeToLiveSeconds="600"
	            memoryStoreEvictionPolicy="LRU" />
	
	    <!-- 这里的 users 缓存空间是为了下面的 demo 做准备 -->
	    <cache
	            name="users"
	            eternal="false"
	            maxElementsInMemory="100"
	            overflowToDisk="false"
	            diskPersistent="false"
	            timeToIdleSeconds="0"
	            timeToLiveSeconds="300"
	            memoryStoreEvictionPolicy="LRU" />
	</ehcache>

session管理器

	/**
     * sessionDAO
     * @return
     */
    @Bean
    public SessionDAO sessionDAO(){
        return new MemorySessionDAO();
    }

    /**
     * cookie信息
     * @return
     */
    @Bean
    public SimpleCookie simpleCookie(){
        SimpleCookie simpleCookie = new SimpleCookie();
        simpleCookie.setName(ShiroHttpSession.DEFAULT_SESSION_ID_NAME);
        simpleCookie.setHttpOnly(true);
        return simpleCookie;
    }

    /**
     * session管理器
     * @param ehCacheManager
     * @return
     */
    @Bean
    public SessionManager sessionManager(EhCacheManager ehCacheManager) {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        sessionManager.setGlobalSessionTimeout(tomcatTimeout * 1000);
        sessionManager.setSessionDAO(sessionDAO());

        sessionManager.setDeleteInvalidSessions(true);
        sessionManager.setSessionValidationSchedulerEnabled(true);

        sessionManager.setSessionIdCookie(simpleCookie());
        sessionManager.setCacheManager(ehCacheManager);
        return sessionManager;
    }

Realm,主要用作认证及授权

    /**
     * 自定义Realm,可以多个
     */
    @Bean
    public ShiroRealm shiroRealm() {
        ShiroRealm shiroRealm = new ShiroRealm();
        shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return shiroRealm;
    }

	@Slf4j
	public class ShiroRealm extends AuthorizingRealm {
	    @Autowired
	    @Lazy
	    private UserRepository userRepository;
	
	    @Override
	    public boolean supports(AuthenticationToken token) {
	        return token instanceof UsernamePasswordToken || token instanceof WeChatToken;
	    }
	
	    @Override
	    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
	        log.info("授权");
	        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
	        return authorizationInfo;
	    }
	
	    @Override
	    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
	        log.info("身份认证");
	        // 获取用户登录账号
	        String username = (String) token.getPrincipal();
	        User userInfo = userRepository.findUserByUsername(username);
	        if (null ==userInfo){
	            return null;
	        }
	        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(userInfo, userInfo.getPassword(), ByteSource.Util.bytes(userInfo.getUsername()), getName());
	        return info;
	    }
	}

SecurityManager 安全管理器,Shiro的核心

    @Bean
    public DefaultWebSecurityManager securityManager(EhCacheManager ehCacheManager) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(shiroRealm());
        securityManager.setCacheManager(ehCacheManager);
        return securityManager;
    }

登录控制器LoginController

	@Api(tags = "登录/登出接口")
	@RestController
	@RequestMapping(Endpoints.API_V1)
	public class LoginController{
	
	    @ApiOperation("用户登录")
	    @PostMapping("/login")
	    public JsonResult login(String username, String password)
	    {
	        /*
	            1、验证用户是否已经登录
	            2.1、已登录则直接返回
	            2.2、未登录则登录验证
	         */
	        Subject currentUser = SecurityUtils.getSubject();
	        User user = (User) currentUser.getPrincipal();
	        if(user != null) {
	            if (!StringUtils.equals(user.getEmail(),username)){
	                currentUser.logout();
	            }else {
	                return JsonResult.OK(ResultCode.SUCCESS.code());
	            }
	        }
	        try {
	            UsernamePasswordToken token = new UsernamePasswordToken(username, password);
	            currentUser.login(token);
	            return JsonResult.OK();
	        }catch( UnknownAccountException uae ) {
	            return JsonResult.Err(ResultCode.UNKNOWN_ACCOUNT);
	        } catch ( IncorrectCredentialsException ice ) {
	            return JsonResult.Err(ResultCode.INCORRECT_CREDENTIALS);
	        } catch ( LockedAccountException lae ) {
	            return JsonResult.Err(ResultCode.LOCKED_ACCOUNT);
	        } catch ( ExcessiveAttemptsException eae ) {
	            return JsonResult.Err(ResultCode.EXCESSIVE_ATTEMPTS);
	        } catch ( AuthenticationException ae ) {
	            return JsonResult.Err(ResultCode.LOGIN_FAIL);
	        }
	    }
	
	    @ApiOperation("用户登出")
	    @GetMapping(Endpoints.LOGOUT)
	    public JsonResult logout()
	    {
	        Subject currentUser = SecurityUtils.getSubject();
	        Object principal = currentUser.getPrincipal();
	        if (principal != null) {
	            currentUser.logout();
	        }
	        return JsonResult.OK();
	    }
	}

本文主要简单介绍了SpringBoot2.0如何将Shiro整合在一起并实现简单的登录验证,后面还将对授权等做进一步讲解。如果有兴趣的朋友想更加细致的了解Shiro可以去看看《跟我学Shiro》张开涛 对整个框架的使用有一定的帮助。

参考文章:

  1. https://blog.csdn.net/engineer_he/article/details/79019089
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

墨隐天涯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值