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”);過濾鏈定義,從上向下順序執行,一般將 / 放在最爲下邊 -->:這是一個坑 一不小心代碼就不好使了