springboot純淨整合shiro的

springboot純淨整合shiro的

shiro介紹

Apache Shiro是一個強大且易用的Java安全框架,執行身份驗證、授權、密碼學和會話管理。使用Shiro的易於理解的API,您可以快速、輕鬆地獲得任何應用程序,從最小的移動應用程序到最大的網絡和企業應用程序。

Apache Shiro 體系結構
在這裏插入圖片描述
1、 Authentication 認證 ---- 用戶登錄
2、 Authorization 授權 — 用戶具有哪些權限

3、 Cryptography 安全數據加密
4、 Session Management 會話管理
5、 Web Integration web系統集成
6、 Interations 集成其它應用,spring、

4.1. 分析Shiro的核心API

Subject: 用戶主體(把操作交給SecurityManager)
SecurityManager:安全管理器(關聯Realm)
Realm:Shiro連接數據的橋樑

集體操作:
1、自定義一個Realm繼承AuthorizingRealm
2、創建配置類,將三個核心互相關聯

編寫自己的realm

package com.itheima.shiro;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
/**
 * 自定義Realm
 *
 */
public class UserRealm extends AuthorizingRealm{
	/**
	 * 執行授權邏輯
	 */
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {
		System.out.println("執行授權邏輯");
		return null;
	}
	/**
	 * 執行認證邏輯
	 */
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken arg0) throws AuthenticationException {
		System.out.println("執行認證邏輯");
		return null;
	}

}

編寫Shiro配置類

package com.itheima.shiro;

import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * Shiro的配置類
 */
@Configuration
public class ShiroConfig {

	/**
	 * 創建ShiroFilterFactoryBean
	 */
    @Bean
	public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager securityManager){
		ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
		
		//設置安全管理器
		shiroFilterFactoryBean.setSecurityManager(securityManager);
		
		return shiroFilterFactoryBean;
	}
	
	/**
	 * 創建DefaultWebSecurityManager
	 */
	@Bean(name="securityManager")
	public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm")UserRealm userRealm){
		DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
		//關聯realm
		securityManager.setRealm(userRealm);
		return securityManager;
	}
	
	/**
	 * 創建Realm
	 */
	@Bean(name="userRealm")
	public UserRealm getRealm(){
		return new UserRealm();
	}
}

使用Shiro內置過濾器實現頁面攔截

package com.itheima.shiro;

import java.util.LinkedHashMap;
import java.util.Map;

import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * Shiro的配置類
 * @author lenovo
 *
 */
@Configuration
public class ShiroConfig {

	/**
	 * 創建ShiroFilterFactoryBean
	 */
	@Bean
	public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager securityManager){
		ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
		
		//設置安全管理器
		shiroFilterFactoryBean.setSecurityManager(securityManager);
		
		//添加Shiro內置過濾器
		/**
		 * Shiro內置過濾器,可以實現權限相關的攔截器
		 *    常用的過濾器:
		 *       anon: 無需認證(登錄)可以訪問
		 *       authc: 必須認證纔可以訪問
		 *       user: 如果使用rememberMe的功能可以直接訪問
		 *       perms: 該資源必須得到資源權限纔可以訪問
		 *       role: 該資源必須得到角色權限纔可以訪問
		 */
		Map<String,String> filterMap = new LinkedHashMap<String,String>();
		/*filterMap.put("/add", "authc");
		filterMap.put("/update", "authc");*/
		filterMap.put("/testThymeleaf", "anon");
		filterMap.put("/*", "authc");
		//修改調整的登錄頁面
		shiroFilterFactoryBean.setLoginUrl("/toLogin");
		shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
		return shiroFilterFactoryBean;
	}
	/**
	 * 創建DefaultWebSecurityManager
	 */
	@Bean(name="securityManager")
	public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm")UserRealm userRealm){
		DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
		//關聯realm
		securityManager.setRealm(userRealm);
		return securityManager;
	}
	/**
	 * 創建Realm
	 */
	@Bean(name="userRealm")
	public UserRealm getRealm(){
		return new UserRealm();
	}
}

登錄邏輯略寫

//1.獲取Subject
		Subject subject = SecurityUtils.getSubject();
		
		//2.封裝用戶數據
		UsernamePasswordToken token = new UsernamePasswordToken(name,password);
		
		//3.執行登錄方法
		try {
			subject.login(token);
			
			//登錄成功
			//跳轉到test.html
			return "redirect:/testThymeleaf";
		} catch (UnknownAccountException e) {
			//e.printStackTrace();
			//登錄失敗:用戶名不存在
			model.addAttribute("msg", "用戶名不存在");
			return "login";
		}catch (IncorrectCredentialsException e) {
			//e.printStackTrace();
			//登錄失敗:密碼錯誤
			model.addAttribute("msg", "密碼錯誤");
			return "login";
		}
	}

/**
	 * 執行認證邏輯
	 */
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken arg0) throws AuthenticationException {
		System.out.println("執行認證邏輯");
		
		//假設數據庫的用戶名和密碼
		String name = "eric";
		String password = "123456";
		
		//編寫shiro判斷邏輯,判斷用戶名和密碼
		//1.判斷用戶名
		UsernamePasswordToken token = (UsernamePasswordToken)arg0;
		if(!token.getUsername().equals(name)){
			//用戶名不存在
			return null;//shiro底層會拋出UnKnowAccountException
		}
		
		//2.判斷密碼
		return new SimpleAuthenticationInfo("",password,"");
	}

thymeleaf和shiro標籤整合使用

6.1. 導入thymeleaf擴展座標

<!-- thymel對shiro的擴展座標 -->
		<dependency>
			<groupId>com.github.theborakompanioni</groupId>
			<artifactId>thymeleaf-extras-shiro</artifactId>
			<version>2.0.0</version>
		</dependency>

6.2. 配置ShiroDialect
在ShiroConfig類裏面添加getShiroDialect方法

/**

  • 配置ShiroDialect,用於thymeleaf和shiro標籤配合使用
    */
    @Bean
    public ShiroDialect getShiroDialect(){
    return new ShiroDialect();
    }

6.3. 在頁面上使用shiro標籤

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>測試Thymeleaf的使用</title>
</head>
<body>
<h3 th:text="${name}"></h3>

<hr/>
<div shiro:hasPermission="user:add">
進入用戶添加功能: <a href="add">用戶添加</a><br/>
</div>
<div shiro:hasPermission="user:update">
進入用戶更新功能: <a href="update">用戶更新</a><br/>
</div>
<a href="toLogin">登錄</a>
</body>
</html>

使用純淨整合的案例:

先看看自定義的Realm

public class ShiroRealm extends AuthorizingRealm {

    Log log = LogFactory.get();

    @Resource
    private SysUserService userService;
    @Resource
    private SysResourcesService resourcesService;
    @Resource
    private SysRoleService roleService;

    /**
     * 提供賬戶信息返回認證信息(用戶的角色信息集合)
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //獲取用戶的輸入的賬號.
        String username = (String) token.getPrincipal();
        User user = userService.getByUserName(username);
        if (user == null) {
            throw new UnknownAccountException("賬號不存在!");
        }
        if (user.getStatus() != null && UserStatusEnum.DISABLE.getCode().equals(user.getStatus())) {
            throw new LockedAccountException("帳號已被鎖定,禁止登錄!");
        }

        // principal參數使用用戶Id,方便動態刷新用戶權限
        return new SimpleAuthenticationInfo(
                user.getId(),
                user.getPassword(),
                ByteSource.Util.bytes(username),
                getName()
        );
    }

    /**
     * 權限認證,爲當前登錄的Subject授予角色和權限(角色的權限信息集合)
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {

        log.info("權限認證");
        // 權限信息對象info,用來存放查出的用戶的所有的角色(role)及權限(permission)
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

        Long userId = (Long) SecurityUtils.getSubject().getPrincipal();

        // 賦予角色
        List<Role> roleList = roleService.listRolesByUserId(userId);
        for (Role role : roleList) {
            info.addRole(role.getName());
        }

        // 賦予權限
        List<Resources> resourcesList = null;
        User user = userService.getByPrimaryKey(userId);
        if (null == user) {
            return info;
        }
        // ROOT用戶默認擁有所有權限
        if (UserTypeEnum.ROOT.toString().equalsIgnoreCase(user.getUserType())) {
            resourcesList = resourcesService.listAll();
        } else {
            resourcesList = resourcesService.listByUserId(userId);
        }

        if (!CollectionUtils.isEmpty(resourcesList)) {
            Set<String> permissionSet = new HashSet<>();
            for (Resources resources : resourcesList) {
                String permission = null;
                if (!StringUtils.isEmpty(permission = resources.getPermission())) {
                    permissionSet.addAll(Arrays.asList(permission.trim().split(",")));
                }
            }
            info.setStringPermissions(permissionSet);
        }
        return info;
    }

}

密碼比較器

public class CredentialsMatcher extends SimpleCredentialsMatcher {

    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
        UsernamePasswordToken utoken = (UsernamePasswordToken) token;
        //獲得用戶輸入的密碼:(可以採用加鹽(salt)的方式去檢驗)
        String inPassword = new String(utoken.getPassword());
        //獲得數據庫中的密碼
        String dbPassword = (String) info.getCredentials();
        try {
            dbPassword = PasswordUtil.decrypt(dbPassword, utoken.getUsername());
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        //進行密碼的比對
        return this.equals(inPassword, dbPassword);
    }
}

shiro配置類

@Configuration
@Order(-1)
public class ShiroConfig {

    @Autowired
    private ShiroService shiroService;
    @Autowired
    private RedisProperties redisProperties;

    @Bean
    public MethodInvokingFactoryBean methodInvokingFactoryBean(SecurityManager securityManager){
        MethodInvokingFactoryBean bean = new MethodInvokingFactoryBean();
        bean.setStaticMethod("org.apache.shiro.SecurityUtils.setSecurityManager");
        bean.setArguments(securityManager);
        return bean;
    }

    @Bean(name = "lifecycleBeanPostProcessor")
    public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    /**
     * ShiroFilterFactoryBean 處理攔截資源文件問題。
     * 注意:單獨一個ShiroFilterFactoryBean配置是或報錯的,因爲在
     * 初始化ShiroFilterFactoryBean的時候需要注入:SecurityManager
     * Filter Chain定義說明
     * 1、一個URL可以配置多個Filter,使用逗號分隔
     * 2、當設置多個過濾器時,全部驗證通過,才視爲通過
     * 3、部分過濾器可指定參數,如perms,roles
     */
    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // 必須設置 SecurityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 如果不設置默認會自動尋找Web工程根目錄下的"/login.jsp"頁面
        shiroFilterFactoryBean.setLoginUrl("/passport/login/");
        // 登錄成功後要跳轉的鏈接
        shiroFilterFactoryBean.setSuccessUrl("/index");
        // 未授權界面;
        shiroFilterFactoryBean.setUnauthorizedUrl("/error/403");
        // 配置數據庫中的resource
        Map<String, String> filterChainDefinitionMap = shiroService.loadFilterChainDefinitions();
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
        creator.setProxyTargetClass(true);
        return creator;
    }

    @Bean(name = "securityManager")
    public SecurityManager securityManager(@Qualifier("shiroRealm") ShiroRealm authRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 設置realm.
        securityManager.setRealm(authRealm);
        securityManager.setCacheManager(redisCacheManager());
        // 自定義session管理 使用redis
        securityManager.setSessionManager(sessionManager());
        // 注入記住我管理器
        securityManager.setRememberMeManager(rememberMeManager());
        return securityManager;
    }

    @Bean(name = "shiroRealm")
    public ShiroRealm shiroRealm(@Qualifier("credentialsMatcher") RetryLimitCredentialsMatcher matcher) {
        ShiroRealm shiroRealm = new ShiroRealm();
        shiroRealm.setCredentialsMatcher(credentialsMatcher());
        return shiroRealm;
    }

    /**
     * 憑證匹配器
     * (由於我們的密碼校驗交給Shiro的SimpleAuthenticationInfo進行處理了
     * 所以我們需要修改下doGetAuthenticationInfo中的代碼;
     * )
     *
     * @return
     */
    @Bean(name = "credentialsMatcher")
    public RetryLimitCredentialsMatcher credentialsMatcher() {
        return new RetryLimitCredentialsMatcher();
    }


    /**
     * 開啓shiro aop註解支持.
     * 使用代理方式;所以需要開啓代碼支持;
     *
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    /**
     * 配置shiro redisManager
     * 使用的是shiro-redis開源插件
     *
     * @return
     */
    public RedisManager redisManager() {
        CustomRedisManager redisManager = new CustomRedisManager();
        redisManager.setHost(redisProperties.getHost());
        redisManager.setPort(redisProperties.getPort());
        redisManager.setDatabase(redisProperties.getDatabase());
        redisManager.setTimeout(redisProperties.getTimeout());
        redisManager.setPassword(redisProperties.getPassword());
        return redisManager;
    }

    /**
     * cacheManager 緩存 redis實現
     * 使用的是shiro-redis開源插件
     *
     * @return
     */
    @Bean
    public RedisCacheManager redisCacheManager() {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        return redisCacheManager;
    }


    /**
     * RedisSessionDAO shiro sessionDao層的實現 通過redis
     * 使用的是shiro-redis開源插件
     */
//    @Bean
    public RedisSessionDAO redisSessionDAO() {
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager());
        return redisSessionDAO;
    }

    /**
     * shiro session的管理
     */
    @Bean
    public DefaultWebSessionManager sessionManager() {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        sessionManager.setGlobalSessionTimeout(redisProperties.getExpire() * 1000L);
        sessionManager.setSessionDAO(redisSessionDAO());
        return sessionManager;
    }

    /**
     * cookie對象;
     *
     * @return
     */
    public SimpleCookie rememberMeCookie() {
        // 這個參數是cookie的名稱,對應前端的checkbox的name = rememberMe
        SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
        // 記住我cookie生效時間30天 ,單位秒。 註釋掉,默認永久不過期 2018-07-15
        simpleCookie.setMaxAge(redisProperties.getExpire());
        return simpleCookie;
    }

    /**
     * cookie管理對象;記住我功能
     *
     * @return
     */
    public CookieRememberMeManager rememberMeManager() {
        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
        cookieRememberMeManager.setCookie(rememberMeCookie());
        //rememberMe cookie加密的密鑰 建議每個項目都不一樣 默認AES算法 密鑰長度(128 256 512 位)
        cookieRememberMeManager.setCipherKey(Base64.decode("1QWLxg+NYmxraMoxAXu/Iw=="));
        return cookieRememberMeManager;
    }

這裏說明一下,這裏使用了redis,和cookie,redis是爲了緩存權限認證, securityManager.setCacheManager(redisCacheManager()),
使用securityManager.setSessionManager(sessionManager());將session管理起來,securityManager.setRememberMeManager(rememberMeManager());將自己我管理。
關於記住我:
1. UsernamePasswordToken token = new UsernamePasswordToken(username, password, rememberMe);rememberMe爲true
2.在shiroFilter中加入filterChainDefinitionMap.put("/", “user”);過濾鏈定義,從上向下順序執行,一般將 / 放在最爲下邊 -->:這是一個坑 一不小心代碼就不好使了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章